Codeforces Round #646 (Div. 2)(A~E)

題目傳送門

A. Odd Selection

分析

  • 題意
  1. 從n個數中選擇m個數,問能使這個m個數的和位奇數?
  • 思路
  1. 首先對於這個n個數我們可以統計出其中的 奇數與偶數 的數量分別設爲 x、y,
  2. 如果要想讓m個數想加位奇數的話,在m個數中,奇數的數量必須是 奇數個,而偶數的數量 奇、偶對答案都不會有影響(前提是 奇數量 + 偶數量 = m),
  3. 那麼一種是思路是我們考慮 奇數的數量 在m個數中,儘可能的多使用奇數,這樣對偶數的需求數量就減少了,這樣更容易得出ans ,首先我們令 x = min(x, m) ,因爲奇數的數量> m是沒有意義的,然後我們要特殊考慮 這個時候如果 x偶數的話,我們在令 x-=1,這樣就一定保證了 使用的奇數數量是 奇數個,
  4. 之後我們只需要檢測 偶數的數量y是否 >= m-x,如果是的話 就輸出yes

代碼

#include <bits/stdc++.h>
using namespace std;
void fre() { freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { freopen("A.txt", "r", stdin);}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define ll long long
#define ull unsigned long long 
#define db double
#define Pir pair<int, int>
#define PIR pair<Pir, Pir>
#define INF 0x3f3f3f3f
#define mod 998244353

const int mxn = 2e5 + 10;
int ar[mxn];

int main()
{
    /* fre(); */
    int T;
    scanf("%d", &T);
    while(T --)
    {
        int n, x;
        scanf("%d %d", &n, &x);
        int odd = 0, eve = 0;
        for(int i = 1; i <= n; i ++)
        {
            scanf("%d", &ar[i]);
            if(ar[i] % 2) odd ++;
            else eve ++;
        }

        odd = min(odd, x);
        if(odd % 2 == 0) odd --;

        if(odd > 0 && odd + eve >= x)
            printf("Yes\n");
        else
            printf("No\n");
    }

    return 0;
}

B. Subsequence Hate(前後綴和)

分析

  • 題意
  1. 給我們一個有0、1組成的字符串s,我們可以對其中的元素進行變化操作,把0變成1或1變成0,問最少需要多少次這樣的變換操作,是s中所有 子序列 中不會出現 010、101 的情況
  • 思路
  1. 我們考慮 什麼樣的 字符串 纔不會出現 子序列存在 010、101的情況?

    1. 元素全爲 0 的字符串
    2. 元素全爲 1 的字符串
    3. 字符串 前半部分全爲0,後半部分全爲1
    4. 字符串 前半部分全爲1,後半部分全爲0
  2. 其中我們 可以認爲 情況 1. 、2. 分別爲 3. 、4. 情況的特殊情況,所以只要考慮 3.、4.情況,

  3. 對於3情況,我們預處理 前綴1的數量p1[i],在預處理後綴0的數量s0[i],這樣我們我們循環遍歷每一個i位置把i位置之前的1都變成0,把i以及其之後的位置的0都變成1,用預處理的 數據計算出花費的代價,在遍歷的時候維護 代價的最小值

  4. 對與4.情況我們 同樣進行預處理,同樣的循環維護最小花費,

  5. 最終 就是兩種的 花費的最小值就是答案

代碼

#include <bits/stdc++.h>
using namespace std;
void fre() { freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { freopen("A.txt", "r", stdin);}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define ll long long
#define ull unsigned long long 
#define db double
#define Pir pair<int, int>
#define PIR pair<Pir, Pir>
#define INF 0x3f3f3f3f
#define mod 998244353

const int mxn = 2e3 + 10;
int p0[mxn], p1[mxn];
int s0[mxn], s1[mxn];
char ar[mxn];

int main()
{
    /* fre(); */
    int T;
    scanf("%d", &T);
    while(T --)
    {
        scanf("%s", ar + 1);
        int n = strlen(ar + 1);
        for(int i = 1; i <= n; i ++)
        {
            p0[i] = p0[i - 1];
            p1[i] = p1[i - 1];
            if(ar[i] == '0')
                p0[i] ++;
            else
                p1[i] ++;
        }

        s0[n + 1] = 0;
        s1[n + 1] = 0;
        for(int i = n; i >= 1; i --)
        {
            s0[i] = s0[i + 1];
            s1[i] = s1[i + 1];
            if(ar[i] == '0')
                s0[i] ++;
            else
                s1[i] ++;
        }
        int ans = INF;
        for(int i = 1; i <= n; i ++)
        {
            ans = min(ans, p1[i - 1] + s0[i]);
            ans = min(ans, p0[i - 1] + s1[i]);
        }
        ans = min(ans, p0[n]);

        printf("%d\n", ans);
    }

    return 0;
}


C. Game On Leaves(貪心+博弈?)

分析

  • 題意
  1. 給我們一個n個節點的無根樹,玩家A、B輪流操作,對於每次操作玩家可以選擇 這棵樹的一個葉子節點,把它摘去,誰先摘去 x 節點那個玩家就獲勝?在問A先手,他們都已最優的狀態比賽情況下
  • 思路
  1. 首先我們 考慮特殊情況,如果 x節點的入度位 0、1時(對應 樹只有一個姐節點、x節點爲葉子🍃節點),玩家A可以直接摘去x,則A必勝
  2. 去除上面特殊情況以後,我們把x看做根節點,最後分出勝負的一定是,還剩下兩個節點(x和 與x相連的一個節點)的時候,這個時候 該那個玩家出手,那個玩家獲勝,
  3. 證明:爲什麼一定是剩餘兩個節點?,我們假設一個例子:

x-1-3
|
4-5-6

我們假設 葉子的節點的深度爲0,那麼其它葉子節點的深度都是確定的,,,
我們之後在假設,A先後 摘取了一個深度位2的節點(3節點),這個時候,聰明的B玩家肯定不會選擇深度爲1節點(如果選擇了話 A直接就贏了),它可定會選擇6節點;之後聰明的A也肯定不會選擇深度爲1的節點,它肯定會選擇深度 > 1 的節點,這樣防止對手直接獲勝,就這樣一直輪流操作下去…,總回出現剩餘兩最後兩個節點的情況,,,

我們在考慮這種情況,玩家a不得不選擇摘去一個深度爲1的節點(假設摘去的節點爲1),但是這個時候另一個玩家b是沒法立刻就直接摘去x節點,因此此時x的度爲2,表明x還不是葉子節點,所以沒法摘,所以b玩家只能被迫摘去深度爲1的🍃葉子節點(設爲2),那麼這個時候又出現了最後兩個葉子節點分勝負的 情況了
3-x-1
|
2
通過兩個特殊的例子 大概的證明出了 最後一定會剩餘2個節點,而其它的節點都被玩家再去了,綜上 當 (n - 1) % 2 爲奇數的時候先手玩家獲勝,否則後手獲勝

代碼

#include <bits/stdc++.h>
using namespace std;
void fre() { freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { freopen("A.txt", "r", stdin);}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define ll long long
#define ull unsigned long long 
#define db double
#define Pir pair<int, int>
#define PIR pair<Pir, Pir>
#define INF 0x3f3f3f3f
#define mod (ll)(1e9 + 7)

const int mxn = 2e5 + 10;

int in[mxn];


int main()
{
    /* fre(); */
    int T;
    scanf("%d", &T);
    while(T --)
    {
        int n, x;
        scanf("%d %d", &n, &x);
        for(int i = 1, u, v; i < n; i ++)
        {
            scanf("%d %d", &u, &v);
            in[u] ++, in[v] ++;
        }
        if(in[x] <= 1 || (n - 1) % 2)
            printf("Ayush\n");
        else
            printf("Ashish\n");

        memset(in, 0, n * 4 + 4);
    }

    return 0;
}

D. Guess The Maximums(交互題 + 二分)

分析

  • 題意
  1. 首先有一個長度爲n的序列A,我們只知道A中元素的值在[1,n]之間(A中元素之間可以相同),之後又給我們k個序列分別爲:s1 s2 … sk,這個k個序列的元素值各不相同(這題的突破口)si序列中的元素值爲 A中元素的下標,先我們從這個k個序列中得出我們的長度爲k的密碼p序列,第i爲密碼爲pi爲 不在si序列中的序列A的下標以外的A中其它元素的最大值,
  2. 我們可以 與題目交互 不超過12次(這個這麼少的次數 能提醒我們用 二分法),對於每次交互,我們 可以輸入一些列的A中元素的下標,系統返回這些下標所代表的最大值,讓我們求出中密碼p序列
  • 思路
  1. 首先用一次 將所有下標輸出,來的出 整個A序列的最大值嗎mx,
  2. 用10次二分去求出 mx 在A中的下標設爲idx,(注意:A中的由於元素可以重複,所以最大值mx可以有多個,說明idx座標也有多個,這並不影響做題,我們只要求出其中一個就行了,這是因爲 k個s序列中的元素各不相同,也就是隻可能有一個si序列包含 idx,對這個序列我們單獨的用 一次詢問 去得出這個位置i出的密碼pi,對於與其它位置它的密碼pj(j != i,1<=j<=k)最大值就已經確定了,就是mx)

代碼

#include <bits/stdc++.h>
using namespace std;
void fre() { freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { freopen("A.txt", "r", stdin);}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define ll long long
#define ull unsigned long long 
#define db double
#define Pir pair<int, int>
#define PIR pair<Pir, Pir>
#define INF 0x3f3f3f3f
#define mod 998244353

const int mxn = 2e3 + 10;
vector<int> s[mxn];

int query(int n)
{
    printf("? %d", n);
    for(int i = 1; i <= n; i ++)
        printf(" %d", i);
    printf("\n");

    fflush(stdout);         //cout.flush();
    int res;
    scanf("%d", &res);
    return res;
}

int n, k, mx;

int find_max()
{
    mx = query(n);
    int l = 1, r = n;
    while(l < r)
    {
        int mid = (l + r) >> 1;

        if(query(mid) != mx)
            l = mid + 1;
        else
            r = mid;
    }
    return l;
}

void get_ans()
{
    vector<int> ans;
    int idx = find_max();       //最大值下標

    for(int i = 1; i <= k; i ++)
    {
        auto it = find(s[i].begin(), s[i].end(), idx);
        if(it == s[i].end())
            ans.pb(mx);
        else
        {
            map<int, int> mk;
            for(auto x : s[i])
                mk[x] = 1;

            printf("? %lu", n - mk.size());
            for(int j = 1; j <= n; j ++)
            {
                if(! mk[j])
                    printf(" %d", j);
            }
            printf("\n");
            fflush(stdout);
            int t_mx;
            scanf("%d", &t_mx);
            ans.pb(t_mx);
        }
    }

    printf("!");
    for(auto x : ans)
        printf(" %d", x);
    printf("\n");

    fflush(stdout);
    string st;
    cin >> st;
}


int main()
{
    /* fre(); */
    int T;
    scanf("%d", &T);
    while(T --)
    {
        scanf("%d %d", &n, &k);
        for(int i = 1, c; i <= k; i ++)
        {
            scanf("%d", &c);
            for(int j = 1, t; j <= c; j ++)
            {
                scanf("%d", &t);
                s[i].pb(t);
            }
        }

        get_ans();

        for(int i = 1; i <= k; i ++)
            s[i].clear();
    }

    return 0;
}


E. Tree Shuffling(dfs)

分析

  • 題意
  1. 給我們一個以1爲根有n個節點的樹,每一個節點有一個值b[i] 爲0或1,之後我們希望通過操作把 每一個節點值 變成對應給出的節點值c[i]爲 0或1,每個節點還有一個屬性就 變化操作所需要的花費a[i],
  2. 對於一次操作,我們可以選擇 一個以u爲節點的子樹中的 某些節點(設節點數量爲x) 令這些節點 b[i] 值進行交換,對於的這次操作的花費時 x * a[u],
  3. 問通過這樣操作,把每個節點的b[i]變化爲c[i],總共需要的最小花費是多少?,如果不能 變化爲 對應的c[i]輸出-1
  • 思路
  1. 從根節點往下開始遞歸,維護每個子樹u的 ct0[u]、ct1[u]的數量, 同時又維護 當前節點 進行變換操作時的最小花費 mon = min(mon, a[u]),如果在回溯的時候發現再次回溯到u節點的時候判讀發現 mon == a[u] 的話(說明這個時候 u節點的費用比u節點的所有祖先的費用花費都小),以u爲根節點的子樹中的每一個節點所需的最小花費時 a[u],,,

代碼

#include <bits/stdc++.h>
using namespace std;
void fre() { freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { freopen("A.txt", "r", stdin);}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define ll long long
#define ull unsigned long long 
#define db double
#define Pir pair<int, int>
#define PIR pair<Pir, Pir>
#define INF 0x3f3f3f3f
#define mod 998244353

const int mxn = 2e5 + 10;
int a[mxn], b[mxn], c[mxn];
int ct1[mxn], ct0[mxn];
vector<int> e[mxn * 2];
ll res = 0;

void dfs(int u, int p, int mon)
{
    mon = min(mon, a[u]);
    for(auto v : e[u])
    {
        if(v == p) continue;
        dfs(v, u, mon);
        ct0[u] += ct0[v];       //這裏是維護 u的子樹中,還剩餘多少個 等待交換值的0節點
        ct1[u] += ct1[v];
    }
    if(b[u] != c[u])
    {
        if(b[u] == 0) ct0[u] ++;
        else          ct1[u] ++;
    }

    if(mon == a[u])            //在回溯的過程中 當前節點的費用==之前所有的祖先中最小的費用,那麼我們就可確定,這個子樹的所有需要變換數字的節點,所用的費用都應該是 a[u]
    {
        int c = min(ct0[u], ct1[u]);
        res += 2LL * c * mon;
        ct0[u] -= c;
        ct1[u] -= c;
    }
}


int main()
{
    /* fre(); */
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i ++)
        scanf("%d %d %d", &a[i], &b[i], &c[i]);

    for(int i = 1, u, v; i < n; i ++)
    {
        scanf("%d %d", &u, &v);
        e[u].pb(v);
        e[v].pb(u);
    }

    dfs(1, 1, INF);
    if(ct0[1] || ct1[1])
        printf("-1");
    else
        printf("%lld", res);

    return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章