Codeforces Round 746 div.2 CDE

这场没打,赛后看了一下题值得补

C. Bakry and Partitioning

题意:给出一棵 \(n\) 节点的树,每个点有权值 \(a_i\),问能否割去至少一条,至多 \(k-1\) 条边,使得割完后各个连通块的权值异或和相等。

显然,如果整棵树的异或和为0,那么任选一条边割得到的两个连通块异或和都相等。(异或的性质)

如果整棵树异或和为 \(x\) ,那么问题就等价于能不能把树拆出三个异或和为 \(x\) 的连通块(三个可以合并成一个,所以能拆成更多块的也一定能拆成三块)

dfs地去找,当某个子树的异或和为 \(x\) 时,记录并将这个子树清零,跑完之后看找到了几个即可

记得对 \(k=2\) 特判

int n, k;
int a[maxn], xs[maxn];
int xsum, ans;
struct Edge{
    int t, nt;
}e[maxn << 1];

int head[maxn], cnt = 0;

inline void add(int x, int y){
    e[++cnt].t = y;
    e[cnt].nt = head[x];
    head[x] = cnt;
}

void dfs(int p, int fa){
    xs[p] = a[p];
    for(int i=head[p];i;i=e[i].nt){
        int v = e[i].t;
        if(v != fa){
            dfs(v, p);
            xs[p] ^= xs[v];
        }
    }
    if(xs[p] == xsum){
        ans++;
        xs[p] = 0;
    }
}

void solve(){
    cin >> n >> k;
    xsum = cnt = ans = 0;
    for(int i=1;i<=n;i++) head[i] = 0;
    for(int i=1;i<=n;i++){
        cin >> a[i];
        xsum ^= a[i];
    }
    for(int i=1;i<n;i++){
        int x, y;
        cin >> x >> y;
        add(x, y);
        add(y, x);
    }
    if(xsum == 0){
        cout << "YES\n";
    }else{
        if(k <= 2){
            cout << "NO\n";
            return;
        }
        dfs(1, 0);
        if(ans > 2) cout << "YES\n";
        else cout << "NO\n";
    }
}

D. Hemose in ICPC ?

题意:交互题,现有一棵 \(n\) 节点的树,每个边有边权(边权未知),可以进行至多12次询问:每次询问一个点集,回复是这个点集中 \(Dist(a,b)\) 的最大值,\(Dist(a,b)\) 定义为 \(a\)\(b\) 路径上所有边权的gcd,求使 \(Dist(a,b)\) 最大的 \(a,b\)

首先可以看出要找的就是权值最大的边,gcd是纯唬人的。

第一次查询先查询所有的点,这样就可以知道最大的边权是多少。

查询次数为12,数据规模 \(n<1000\),如果我们能以某种方式把树映射到序列上,且序列上连续的点在树上也连续,就可以二分地询问,找最大边的位置(因为连续,所以序列上两点之间就是边)。

欧拉序即可保证连续性。

ps:一开始用dfs序糊里糊涂过了,不能保证连续性但也能通过某种玄学的巧合AC,正确性不是很显然,我也讲不清楚,就不介绍了

int n;
struct Edge{
    int fr, t, nt;
}e[maxn << 1];

int head[maxn], cnt = 0;
int vis[maxn], vp = 0;
int a[maxn << 1];

inline void add(int x, int y){
    e[++cnt].t = y;
    e[cnt].fr = x;
    e[cnt].nt = head[x];
    head[x] = cnt;
}

void dfs(int p, int fa){
    a[++cnt] = p;
    for(int i=head[p];i;i=e[i].nt){
        int v = e[i].t;
        if(v != fa){
            dfs(v, p);
            a[++cnt] = p;
        }
    }
}

int getans(int l, int r){
    vp++;
    int k = 0, res;
    for(int i=l;i<=r;i++){
        if(vis[a[i]] != vp) vis[a[i]] = vp, k++;
    }
    cout << "? " << k;
    for(int i=1;i<=n;i++){
        if(vis[i] == vp) cout << ' ' << i;
    }
    cout << endl;
    fflush(stdout);

    cin >> res;
    return res;
}

void solve(){
    cin >> n;
    cnt = 0;
    for(int i=1;i<n;i++){
        int x, y;
        cin >> x >> y;
        add(x, y);
        add(y, x);
    }
    cnt = 0;
    dfs(1, -1);

    int mx;
    cout << "? " << n << ' ';
    for(int i=1;i<n;i++) cout << i << ' ';
    cout << n << endl;
    fflush(stdout);
    cin >> mx;

    int l = 1, r = cnt, mid;
    while(l + 1 < r){
        mid = (l + r) / 2;
        if(getans(l, mid) == mx){
            r = mid;
        }else{
            l = mid;//二分部分小改一下 因为要找的是两点间的连边
        }
    }
    cout << "! " << a[l] << ' ' << a[r] << '\n';
}

E. Bored Bakry

题意:给一个长度为 \(n\) 的序列 \(a\),找到最长的子区间 \(a_l..a_r\) 使得子区间所有元素的与运算和大于异或和

对每一位考虑,在第 \(k\) 位上,与和为1的条件是区间所有数第 \(k\) 位全是1;异或和为1的条件是区间中第 \(k\) 位为1的数量是奇数个

那么可以注意到如果区间长度为奇数,任一位上与和为1的时候异或和也一定为1,与和不可能大于异或和

现在考虑,对于特定一个区间,如何判断它是否满足条件:

  • 首先它的长度必须为偶数
  • 从高位到低位枚举,如果它在第 \(k\) 位时第一次满足了区间第 \(k\) 位全为1,那么比 \(k\) 更高的位上1数量必须都为偶数

可以维护一个前缀异或和(记为 \(xsum\)),从高位到低位枚举到第 \(k\) 位时,\(xsum(i)\)表示仅考虑前 \(k-1\) 位值时,前 \(i\) 位的异或和

第二个条件其实就相当于 \(xsum(r) = xsum(l-1)\),因为前面这些位上的1都是偶数个

那么做法就很显然了:从高位到低位枚举,每一层中对于每一个1的位置 \(r\),去找最小的满足条件的 \(l\),同时维护 \(xsum\) 即可

关于找最小的 \(l\) ,记录同一段连续1内每一个 \(xsum\) 值最早出现的位置即可(详见代码)

int gtw(int x, int w){ return (x >> (w-1)) & 1;}

int n, a[maxn], p[maxn];
int xsum[maxn], pos[maxn * 2];

void solve(){
    cin >> n;
    for(int i=1;i<=n;i++){
        cin >> a[i];
    }

    int ans = 0;
    for(int k=21;k>=1;k--){
        int l = 1, r;
        while(l < n){
            while(l <= n && gtw(a[l], k) == 0) l++;
            if(l > n) break;
            r = l + 1;
            while(r <= n && gtw(a[r], k) == 1) r++;
            r--;
            for(int j=l-1;j<=r;j+=2) pos[xsum[j]] = -1;
            for(int j=l-1;j<=r;j+=2){
                if(pos[xsum[j]] == -1) pos[xsum[j]] = j;
                else ans = max(ans, j - pos[xsum[j]]);
            }

            for(int j=l;j<=r;j+=2) pos[xsum[j]] = -1;
            for(int j=l;j<=r;j+=2){
                if(pos[xsum[j]] == -1) pos[xsum[j]] = j;
                else ans = max(ans, j - pos[xsum[j]]);
            }
            l = r + 1;
        }
        for(int i=1;i<=n;i++){
            p[i] += a[i] & (1 << (k-1));
            xsum[i] = xsum[i-1] ^ p[i];
        }
    }
    cout << ans << '\n';
}

Codeforces Round 746 div.2 CDE
http://www.lxtyin.ac.cn/2021/11/17/题解/Codeforces Round 746 div.2/
作者
lx_tyin
发布于
2021年11月17日
许可协议