题意:给你一棵树,你选择删掉一条边,再加上一条边(也要保证为树),问最后树上的节点能够两两完美匹配的加删边方案数?
n<=5e5.
标程:
1 #include<cstdio> 2 #include<vector> 3 #include<algorithm> 4 using namespace std; 5 int read() 6 { 7 int x=0;char ch=getchar(); 8 while (ch<'0'||ch>'9') ch=getchar(); 9 while ('0'<=ch&&ch<='9') x=(x<<1)+(x<<3)+ch-'0',ch=getchar(); 10 return x; 11 } 12 const int N=500005; 13 typedef long long ll; 14 int size[N],n,u,v; 15 ll ans; 16 vector<int> vec[N]; 17 struct node{int a[2][2];}f[N],g[N]; 18 vector<node> pre[N]; 19 void init(node &x){x.a[0][0]=1;x.a[0][1]=x.a[1][0]=x.a[1][1]=0;} 20 node modi(node x,node y) 21 { 22 node c; 23 c.a[0][0]=x.a[0][0]*y.a[1][0]; 24 c.a[1][0]=x.a[0][0]*y.a[0][0]+x.a[1][0]*y.a[1][0]; 25 c.a[0][1]=x.a[0][1]*y.a[1][0]+x.a[0][0]*(y.a[1][1]+y.a[0][0]); 26 c.a[1][1]=x.a[1][1]*y.a[1][0]+x.a[1][0]*(y.a[1][1]+y.a[0][0])+x.a[0][0]*y.a[0][1]+x.a[0][1]*y.a[0][0]; 27 return c; 28 } 29 node merge(node x,node y) 30 { 31 node c; 32 c.a[0][0]=x.a[0][0]*y.a[0][0]; 33 c.a[1][0]=x.a[1][0]*y.a[0][0]+x.a[0][0]*y.a[1][0]; 34 c.a[0][1]=x.a[0][1]*y.a[0][0]+x.a[0][0]*y.a[0][1]; 35 c.a[1][1]=x.a[1][1]*y.a[0][0]+x.a[1][0]*y.a[0][1]+x.a[0][1]*y.a[1][0]+x.a[0][0]*y.a[1][1]; 36 return c; 37 } 38 void dp1(int x,int fa) 39 { 40 size[x]=1;init(f[x]); 41 if (fa!=-1) vec[x].erase(find(vec[x].begin(),vec[x].end(),fa));//将fa删去,方便前后缀的处理 42 for (int i=0;i<vec[x].size();i++) 43 { 44 int v=vec[x][i]; //注意内部定义 45 dp1(v,x);size[x]+=size[v]; 46 f[x]=modi(f[x],f[v]); 47 } 48 } 49 void dp2(int x,int fa) 50 { 51 node cur;init(cur); 52 if (fa>0) pre[x].push_back(modi(cur,g[x]));else pre[x].push_back(cur); 53 for (int i=0;i<vec[x].size();i++) 54 pre[x].push_back(modi(pre[x].back(),f[vec[x][i]])); 55 for (int i=vec[x].size()-1;i>=0;i--) 56 { 57 int v=vec[x][i]; 58 g[v]=merge(pre[x][i],cur);cur=modi(cur,f[v]); 59 dp2(v,x); 60 } 61 } 62 int main() 63 { 64 n=read();if (n&1) return puts("0"),0; 65 for (int i=1;i<n;i++) u=read(),v=read(),vec[u].push_back(v),vec[v].push_back(u); 66 dp1(1,-1);dp2(1,-1); 67 for (int i=2;i<=n;i++) 68 if (f[i].a[1][0]&&g[i].a[1][0]) ans+=(ll)size[i]*(n-size[i]); 69 else ans+=(ll)(f[i].a[0][0]+f[i].a[1][1])*(g[i].a[0][0]+g[i].a[1][1]); 70 printf("%lld ",ans); 71 return 0; 72 }
易错点:1.转移式子要认真推。
2.注意函数内部定义变量。
3.将fa在vector中删去,方便前后缀寻址的对应。
题解:dp
把一条边删掉,树就分成了两个子树。要么各自匹配(这样怎么连都可以),要么连一条边后边的端点匹配,也就是两个子树在没有连边前各有一个点没有匹配。
f[i][0/1][0/1]表示以i为根的子树,根是否被匹配,子树中是否恰有一个点未被匹配的未匹配点的选法数。
g表示i为根的子树外的部分的答案(以fa[i]为根)。正反dp两遍后统计答案即可。