Codeforces Round # 746-Div. 2-C Bakry and Partitioning题解

Codeforces Round # 746-Div. 2-C Bakry and Partitioning题解

题目链接

题目链接放在Bakry and Partitioning,大意就是给一棵有 n n n个节点的树,树上每个节点 i i i都带有一个权值 a i a_i ai,现在要把这棵树分成满足条件的x个联通分支(2 ⩽ x ⩽ k , k \leqslant x\leqslant k,k xk,k为题目给定的值):每个联通分支所有节点的权值做异或运算后得到一个值而且所有 x x x个值要相等。如果存在满足条件的分法则输出 Y E S YES YES,否则输出 N O NO NO

题目分析

先说下题目思路,由异或的运算规则可以得到两条性质:

  1. ∀ x ∈ N , x ( n ) : = x   x o r   x ⋯ x o r   x \forall x\in \mathbf N,x^{(n)}:=x\ \mathrm{xor}\ x\cdots\mathrm{xor}\ x xN,x(n):=x xor xxor x(一共 n n n x x x),则当 n n n为偶数时, x ( n ) = 0 x^{(n)}=0 x(n)=0;当 n n n为奇数时, x ( n ) = x x^{(n)}=x x(n)=x.
  2. x   x o r   0 = x x\ \mathrm{xor}\ 0=x x xor 0=x.

由此我们可以得到,当给定的数可被分为偶数个满足条件的联通分支时,所有节点的异或值为0(充要条件)。我们只需要将树分为任意的两个联通分支即可;当给定给定的数只可被分为奇数个满足条件的联通分支时,此时设所有节点的异或值为 x ( x ≠ 0 ) x(x\neq0) x(x=0),我们在树中需要找到两支互不相交的异或值均为 x x x的联通分支,然后将它们与树的其余部分分开,从而形成三个异或值为 x x x的联通分支,如果找不到的话说明没有满足题目要求的分法。
具体实现步骤如下:

  1. 读取数据并建图,然后对所有节点的权值求异或,最后得到值 x x x,转步骤2.
  2. 如果 x = 0 x=0 x=0,则直接输出 Y E S YES YES,转步骤1;否则转步骤3.
  3. 如果 k = 2 k=2 k=2,则直接输出 N O NO NO,转步骤1;否则转步骤4.
  4. 1 1 1为根节点对树进行 D F S DFS DFS,在遍历同时寻找互不相交的异或值等于 x x x的联通分支个数 n u m num num.如果 n u m > = 2 num>=2 num>=2,则输出 Y E S YES YES,转步骤1;否则输出 N O NO NO,转步骤1.

源代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N=1e5+5;
int t,n,k,a[N],tree[N],head[N],to[N*2],cnt,num,xorall=0;
bool vis[N];
vector<int>nxt(N<<1,0);
void X_OR(int x,int pre)
{
    tree[x]=a[x];
    for(int i=head[x];i;i=nxt[i])
    {
        int &nod=to[i];
        if(nod==pre) continue;
        X_OR(nod,x);
        tree[x]^=tree[nod];
    }
    if(tree[x]==xorall) num++,tree[x]=0;
    return;
}
inline void addedge(int u,int v)
{
    nxt[++cnt]=head[u];
    head[u]=cnt;
    to[cnt]=v;
    return;
}
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&k);
        memset(head,0,sizeof head);
        nxt.push_back(0);
        cnt=num=xorall=0;
        for(int i=1;i<=n;i++) scanf("%d",a+i),xorall^=a[i];
        for(int i=1;i<n;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            addedge(u,v);
            addedge(v,u);
        }
        if(!xorall) {printf("YES\n");continue;}
        X_OR(1,0);
        nxt.clear();
        if(k==2) printf("NO\n");
        else if(num>1) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}

吐槽

在比赛中拿到这个题时我本来打算用并查集做的后来发现并不行,然后就白白的浪费了时间;在比赛结束后,我试着开普通数组做这道题,结果ac了但耗费了600多ms,后来才发现memset函数非常耗时!!! 一般对于 1 0 5 ∼ 1 0 6 10^5\sim10^6 105106这种长度的数组使用 m e m s e t memset memset函数时,耗时已经非常可观了,为此我思考了一下,决定取消每次对链式前向星的 t o to to数组使用该函数,然后把 n x t nxt nxt数组改成了 v e c t o r vector vector,这才把时间降到不到300ms。至于 h e a d head head数组我没想好怎么改,如果有大神能指点的话感激不尽。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值