zoukankan      html  css  js  c++  java
  • [atAGC052F]Tree Vertices XOR

    结论

    注意到如果$x$周围有偶数个1,对$x$操作显然不会改变$a_{x}$,因此不妨强制操作的点周围要有奇数个1,不难发现此时恰好会改变该点,即令$a_{x}=a_{x}oplus 1$

    称${a_{i}}$合法当且仅当其能被得到,问题即统计合法序列数

    显然操作是可逆的,因此${a_{i}}$合法等价于其能通过操作使得$forall 1le ile n,a_{i}=1$

    结论1:若${a_{i}}$能通过操作得到${b_{i}}$,两者合法性相同

    根据操作的可逆性显然成立

    称${a_{i}}$的"1子图"为所有所有满足$a_{i}=1$​的点的导出子图,考虑以下结论——

    结论2:操作不会改变1子图连通块数的奇偶性

    不妨假设操作$x$,并记与$x$相邻的点中有$s$个1,则有$sequiv 1(mod 2)$

    对$a_{x}$的变化分类讨论,不难得到连通块数即$pm (s-1)$,而$s-1$为偶数,也即不会改变奇偶性

    另一方面,注意到最终$forall 1le ile n,a_{i}=1$时1子图的连通块数为1,因此不难得到${a_{i}}$合法的(一个)必要条件为${a_{i}}$的1子图连通块数为奇数

    另外,${a_{i}}$合法的另一个必要条件即存在一个点可以操作(周围有奇数个点为1)

    (特别的,在$forall 1le ile n,a_{i}=1$时,由于$nge 3$,总存在叶子,该点即满足条件)

    事实上,若原树存在一个点满足其度数$ge 3$且将其删除后存在两个连通块点数$ge 2$,那么上述的两个必要条件即是充分的,下面将来证明这一点(过程略长,跳过证明可以点击这里)——

    证明

    下面,先给出一些引理,来对题目做一些转换——

    引理1:若${a_{i}}$的1子图非空且连通,则${a_{i}}$合法

    如果$forall 1le ile n,a_{i}=1$显然合法,否则任取一个$a_{x}=0$的点$x$和一个$a_{y}=1$的点$y$

    考虑$x$到$y$的路径,根据介值定理其中总存在一条边$(x',y')$满足$a_{x'}=0$且$a_{y'}=1$

    此时,若$x'$的其余某条出边满足$a_{z}=1$,显然$y'$和$z$无法连通,与1子图连通矛盾

    换言之,$a_{x'}$的出边中仅有$a_{y'}=1$(其余都为0),那么操作$x'$即可使$a_{x'}=1$

    注意到$x'$和$y$直接相连,因此仍然满足条件,重复此过程即可使$forall 1le ile n,a_{i}=1$

    引理2:将${a_{i}}$的1子图每一个连通块中仅保留一个1(其余点都变为0),不影响${a_{i}}$合法性

    任取1子图的一个连通块,显然该连通块外与连通块内有边的点必然都是0,注意到这些0如果不操作,那么整个连通块即是独立的(连向恒为0的点可以看作不连)

    此时,这个子问题${a_{i}}$的1子图非空且连通(仅有一个1),根据引理1可以操作使得整个连通块均为1,根据结论1也即合法性相同

    此时,1子图的每一个连通块仅有一个点,也即不存在$(x,y)in E$使得$a_{x}=a_{y}=1$

    为了保持此性质,构造两种新操作——

    移动操作:选择一个$a_{x}=1$和$(x,y)in E$,若$y$周围不存在1,则交换$a_{x}$和$a_{y}$(注意必然有$a_{y}=0$)

    合并操作:选择一个$a_{x}=0$且$x$周围恰存在$ge 3$个1,将这些1全部变为0并令$a_{x}=1$

    (在性质成立时)新操作都可以被原操作得到,且可以保持性质成立

    重新考虑存在一个点可以操作的条件,显然等价于可以执行移动或合并操作

    另一方面,注意到移动操作不会改变(1子图)连通块数、合并操作会减少连通块数,因此不妨证明如果可以执行移动或合并操作和且1子图连通块数$ge 3$,经过若干次操作后可以执行合并操作

    注意到移动或合并操作后一定可以再执行移动操作,因此即可重复上述过程不断执行合并操作,再根据连通块数为奇数,最终即可使得连通块数为1,进而根据引理1即合法

    为了说明上述事情,再给出一些引理——

    引理3:若树中存在一个点可以操作,那么一定可以通过若干次操作后改变$a_{rt}$

    移动操作可以看作周围恰有1个1的合并操作,因此此证明中将两者都称为合并操作

    若$a_{rt}=0$且$rt$周围有奇数个1,直接对$rt$执行合并操作即可

    若$rt$周围有偶数个1,考虑这个可以执行的操作必然在$rt$某个儿子的子树内部,归纳此结论并由此改变该儿子,进而即有奇数个1,再对$rt$执行合并操作即可

    若$a_{rt}=1$,那么$rt$周围必然都是0,且若其中一个儿子有偶数个儿子为1(算上$rt$即周围有奇数个1),那么直接对该点执行合并操作即可

    若不存在上述的儿子,可以执行的操作必然在某个儿子的儿子子树内部,根据归纳将该儿子的儿子修改,再对该儿子执行合并操作即可

    引理4:若树中不能执行移动或合并操作、$a_{rt}=0$且树中存在至少一个1,那么若增加一个$a_{rt}$的父亲且其权值为1,那么经过若干次操作后可以执行合并操作

    若$rt$的所有儿子均为0,直接移动到$a_{rt}$后可以看作$rt$某个子树内含有1的儿子的子问题,归纳此结论即可在该子问题内执行合并操作

    若$rt$存在奇数个儿子为1,显然初始可以执行操作,与条件矛盾

    若$rt$存在偶数个儿子为1,那么即可对$rt$执行合并操作,即结论成立

    由此,选择一个点$rt$满足其度数$ge 3$且将其删除后存在两个连通块点数$ge 2$,并以其为根建树

    先根据引理3,若$a_{rt}=1$则修改$a_{rt}=0$,再对为1的儿子数分类讨论:

    1.若为偶数,任选一个子树内可以操作的儿子,根据引理3修改该儿子,即变为奇数的情况

    2.若为奇数且$ge 3$,直接对$rt$执行合并操作即可

    3.若为1,考虑其余儿子的子树,分类讨论——

    (1)若存在两个子树内可以操作,将这两个子树的根由引理3修改,然后即可对$rt$执行合并操作

    (2)若存在某个子树内不全为0且无法执行操作,那么将这个为1的儿子移动到根后,该子树即满足引理4,也即可在该子树内执行合并操作

    (3)不为以上情况,即至多一个子树内可以操作,其余子树全部为0,那么不全为0的子树至多只有两个,因此可以将这个为1的儿子移动到某个全部为0的子树

    此时,若满足之前的(1)或(2)则继续按照(1)或(2)执行,否则即仍为(3),那么若这个1所在的子树大小为1,其他子树中不全为0的子树至多只有一个,因此可以将这个1再移动到某个全部为0且点数$ge 2$的子树

    若不存在某个子树可以操作,不难发现整棵树此时仅含有这1个1,也即连通块数为1,与条件矛盾

    否则再根据引理3,将那个可以操作的子树的根(即$rt$的儿子)变为1,并将原来的1向下移动一步,使这个新的1也移动到某个全部为0的子树(这个子树大小不要求$ge 2$)

    此时,若满足之前的(1)或(2)则继续按照(1)或(2)执行,否则即仍为(3),不难发现此时整棵树仅含有这两个1,也即连通块数为2,与条件矛盾

    由此,即说明了可以执行合并操作

    综上,即得证

    总结

    由此,问题即可用树形dp解决,令$f_{k,0/1,0/1,0/1,0/1}$表示以$k$为根的子树中,$a_{k},igoplus_{son}a_{son},$1子图连通块个数奇偶性和是否存在一个点可以操作(不包括$k$)为给定值时的方案数,显然容易转移

    另外,当不存在之前所述的点时,不难发现原树必然为一条链、链的两端额外有若干条出边

    结论3:若${a_{i}}$合法,存在一种方案使得不将链的端点从1变成0,使得$forall 1le ile n,a_{i}=1$

    如果存在,取最后一次此类操作,并不妨假设是左端点

    由于其最终为1,因此之后必然要再操作将其变回1,而中间必然要再操作一个与其相邻的点(否则没有意义),注意到之前的点无法操作,因此只能操作链上出边的点

    以此类推,最终即需要操作右端点,注意到之前的点无法操作,因此右端点之后必须再操作一次,其中总有一次是从1变成0,即与与最后一次矛盾

    因此,当两个端点全为1时,将链中间的点划分为若干个极长全0或1的区间,显然其中只有长度大于1的区间端点可以操作(注意整条链的端点不能操作),即不会减少区间数,因此只能初始全部为1

    对于额外的出边,显然是任意的,只需要将权值为0的再操作一次即可

    当端点中有0时,如果端点可以操作,不妨直接操作并根据结论1,两者合法性相同

    如果不可以操作端点,那么当整条链全为0时即无解,否则将与端点相连的极长的一段0依次操作即可(最后操作端点),同样根据结论1合法性相同

    根据以上的分析,假设链长为$l$(指链上的点数),链的两个端点额外的出边数分别为$x$和$y$(为了方便,不妨假设$x,y e 0$,否则可以将原来的端点作为额外的出边),对端点分类讨论:

    1.若两端点都为1,此时需要保证链上全部为1,方案数即为$2^{x+y}$

    2.若两端点中恰有一个1,那么链上只能有与其中一个端点相连的一段0,注意到不论长度如何,都要保证该端点可以/不可以操作,因此方案数即为$(l-1)2^{x+y}$(注意这已经乘上端点的2了)

    3.若两端点中没有0,对是否全部为0分类讨论——

    (1)若全部为0,那么即需要保证恰有一个端点可以操作,方案数即为$2^{x+y-1}$

    (2)若不全部为0,同样每一组方案对应于唯一的能否操作,方案数即为${l-1choose 2}2^{x+y-2}$

    (对于$l=1$的情况可以特判,但不难发现答案是相同的)

    时间复杂度均为$o(n)$,可以通过

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define N 200005
     4 #define mod 998244353
     5 #define ll long long
     6 #define Add(x,y) x=(x+y)%mod
     7 struct Edge{
     8     int nex,to;
     9 }edge[N<<1];
    10 int n,E,x,y,flag,ans,mi[N],head[N],d[N],sz[N],g[2][2][2][2],f[N][2][2][2][2];
    11 void add(int x,int y){
    12     edge[E].nex=head[x];
    13     edge[E].to=y;
    14     head[x]=E++;
    15 }
    16 void dfs(int k,int fa){
    17     sz[k]=f[k][0][0][0][0]=f[k][1][0][1][0]=1;
    18     int tot=0;
    19     for(int i=head[k];i!=-1;i=edge[i].nex){
    20         int u=edge[i].to;
    21         if (u!=fa){
    22             dfs(u,k);
    23             tot+=(sz[u]>=2),sz[k]+=sz[u];
    24             memcpy(g,f[k],sizeof(g));
    25             memset(f[k],0,sizeof(f[k]));
    26             for(int a=0;a<2;a++)
    27             for(int b=0;b<2;b++)
    28             for(int c=0;c<2;c++)
    29             for(int d=0;d<2;d++)
    30             for(int aa=0;aa<2;aa++)
    31             for(int bb=0;bb<2;bb++)
    32             for(int cc=0;cc<2;cc++)
    33             for(int dd=0;dd<2;dd++)
    34             Add(f[k][a][b^aa][c^cc^(a&aa)][d|dd|(a^bb)],(ll)g[a][b][c][d]*f[u][aa][bb][cc][dd])%mod;
    35         }
    36     }
    37     if (n-sz[k]>=2)tot++;
    38     if ((d[k]>=3)&&(tot>=2))flag=1; 
    39 }
    40 int main(){
    41     mi[0]=1;
    42     for(int i=1;i<N;i++)mi[i]=2*mi[i-1]%mod;
    43     scanf("%d",&n);
    44     memset(head,-1,sizeof(head));
    45     for(int i=1;i<n;i++){
    46         scanf("%d%d",&x,&y);
    47         d[x]++,d[y]++;
    48         add(x,y),add(y,x);
    49     }
    50     dfs(1,0);
    51     if (flag){
    52         for(int a=0;a<2;a++)
    53             for(int b=0;b<2;b++)
    54                 for(int d=0;d<2;d++)
    55                     if (b|d)Add(ans,f[1][a][b][1][d]);
    56         printf("%d
    ",ans);
    57         return 0;
    58     }
    59     x=y=1;
    60     for(int i=1;i<=n;i++)
    61         if (d[i]>=3){
    62             if (x==1)x=d[i]-1;
    63             else y=d[i]-1;
    64         }
    65     int l=n-x-y;
    66     printf("%d
    ",((ll)l*mi[x+y]+mi[x+y-1]+(ll)(l-1)*(l-2)/2%mod*mi[x+y-2])%mod);
    67     return 0;
    68 } 
    View Code
  • 相关阅读:
    JavaScript.how-to-debug-javascript
    iOS.mach_absolute_time
    Startup.国外新锐公司及其技术Blog
    Android.FamousBlogs
    iOS.PrototypeTools
    Android.API.Context.getFilesDir()
    Android.Tools.Ant
    Tools.OnlineAPIs
    Java.Class
    Android.StructureOfAndroidSourceCodeRootTree
  • 原文地址:https://www.cnblogs.com/PYWBKTDA/p/15307571.html
Copyright © 2011-2022 走看看