NOIp模拟题 植树方案 题解【2-SAT】【并查集】

作者: wjyyy 分类: 2-SAT,二进制,图论,并查集,解题报告 发布时间: 2018-08-26 16:55

点击量:18

 

    一个类似2-SAT的思想,但是简化了很多。只需要用到并查集实现。

题目描述

企鹅国打算种一批树。所谓树,就是由\(N\)个结点与\(N-1\)条边连接而成的连通无向图。企鹅国的国王对于这些树有下列要求:

1. 树没有指定根,但它的形态是给定的(即这\(N-1\)条边是给出的);
2. 树的每条边上可以放置一朵花(当然也可以不放置)
3. 共\(Q\)条约束,第\(i\)组约束规定:标号\(u[i]\)的结点到标号\(v[i]\)的结点的简单路径上,花的数量为奇数或偶数。

 

现在,国王想事先知道他最多能种多少棵不一样的树(两棵树被视为不一样当且仅一棵树中某条边的放花情况与另一棵树不相同)。

输入输出格式

输入格式:

第\(1\)行两个整数\(N,Q\);

 

接下来\(N-1\)行,每行两个整数\(u,v\),表示\(u,v\)间存在一条边;

 

接下来\(Q\)行,每三个整数\(u[i],v[i],k[i]\),若\(k[i]\)为$0$,表示\(u[i]\)到\(v[i]\)的简单路径的上花的个数为偶数, 否则为奇数。

输出格式:

一行一个整数,表示能种的不一样的树的种树,对\(998244353\)取模。

输入输出样例

输入样例#1:

5 3
1 2
1 3
2 4
2 5
1 4 0
1 5 1
1 3 0

输出样例#1:

2

说明

\(20\%\)的数据:\(N, Q\le 20\);

\(40\%\)的数据:\(N, Q\le 100\);

\(70\%\)的数据:\(N, Q\le 5000\);

\(100\%\)的数据:\(N, Q\le 100000, 0\le k[i]\le 1\)。

题解:

    这里两点之间的路径为奇数还是偶数与树上唯一一条路径有关,如下图,为了简化问题,我们把奇数和偶数定义为异或和为1和0。

    两点间唯一路径就是这条黄色的路径。我们发现,要保证这条路径的异或和等于给定值,那么它们分别到根的异或和的异或值也是等于给定值的,因为\(x\mathrm{xor} x=0\)。因此要满足\(x\)到\(y\)这条路径的异或和等于给定值,那么只要满足两个点到根的异或和相异或等于给定值就可以了,也就相当于把上面重合的地方消掉了。

 

    问题转化为异或,我们就不用关心树上的关系了也就是说这棵树废掉了。只需要给每个点一个到根的异或和(0/1)就可以了。

 

    那么根据给出的询问信息,我们需要知道哪些点必须和哪些点到根的异或和相同,哪些点和哪些点相反。我们要把每个点拆成两个部分,一部分关联相同,另一部分关联的可以不同。样例构造出来就是这样:

    那么一个连通块就代表了同一种到根的异或和,一对连通块(可以发现连通块总是对称的)代表了一对相反的到根的异或和。而每个节点真正到根的异或和是不带’的节点所在的连通块。因此我们只需要保证每个连通块取相同的值,每对连通块取对应的值就可以了。而每对连通块都有两种取值选择,如果相反,就是01/10,如果相同,就是00/11。那么我们一共有2连通块对数种选择。不过有一点要注意,1到根的异或和一定是0,所以有一组是不能任选的。所以我们有2连通块对数-1种选择。

 

    判断连通块个数,用并查集就好了。

 

Code:

#include<cstdio>
#include<cstring>
int s[200100];
int my_find(int x)
{
    if(s[x]==x)
        return x;
    return s[x]=my_find(s[x]);
}
void my_union(int x,int y)
{
    s[my_find(y)]=my_find(x);
}
bool app[200100];
int qpow(int x,int y)
{
    long long ans=1,m=x;
    while(y)
    {
        if(y&1)
            ans*=m;
        ans%=998244353;
        m*=m;
        m%=998244353;
        y>>=1;
    }
    return ans;
}
int main()
{
    int n,q;
    int u,v,w;
    scanf("%d%d",&n,&q);
    for(int i=1;i<n;++i)
    {
        s[i]=i;
        scanf("%d%d",&u,&v);//可以直接过滤掉
    }
    for(int i=n;i<=2*n;++i)
        s[i]=i;
    for(int i=1;i<=q;++i)
    {
        scanf("%d%d%d",&u,&v,&w);
        if(!w)
        {
            my_union(u,v);//上面图片连边的过程
            my_union(u+n,v+n);
        }
        else
        {
            my_union(u,v+n);
            my_union(u+n,v);
        }
    }
    int cnt=0;
    for(int i=1;i<=2*n;++i)
        if(!app[my_find(i)])
        {
            app[my_find(i)]=1;
            ++cnt;
        }
    printf("%d\n",qpow(2,cnt-1>>1));//因为所有都统计了,所以要减1除以2
    return 0;
}

 

说点什么

avatar
  Subscribe  
提醒
/* */