洛谷题目传送门c++
神仙思惟题仍是要写点东西才好。数组
每次操做把相邻且同色的点反色,直接这样思考会发现状态有很强的后效性,没办法考虑转移。spa
由于树是二分图,因此咱们转化模型:在树的奇数层的全部点上都有一枚棋子,每次能够将棋子移向相邻的空位,目标状态是树的偶数层的全部点上都有棋子。code
这样的互换总次数有没有一个下界呢?排序
咱们求出\(a_i\)表示点\(i\)子树中棋子数量与空位数量之差(能够是负数),那么\(i\)的父边就至少要交换\(|a_i|\)次。ip
为何呢?子树里面空位比棋子少的话,确定要经过父边把\(a_i\)个棋子送出去,才能移进来\(a_i\)个空位。反之亦然。get
因而,若是\(a_{rt}\)(根)不为\(0\)就无解,不然\(\sum\limits_{i=1}^n|a_i|\)就是答案下界。it
而后,仔细推一下发现它就是答案。class
对于一个点,它的父边和全部子边的移进移出的顺序,和其它点的顺序是互相独立的。im
等于说咱们总能安排一个合法的顺序,使得它们一进一出一进一出。。。这样完整地衔接起来,而不会互相矛盾。
显然的想法:把整个环当作根,不在环上的点的答案照算不误。因此把多出来一条边去掉作好树形DP。
首先这仍是一个二分图,所以\(a_{rt}\)不为\(0\)一样无解。
因而,彷佛这条边存在的意义只有分摊一部分转移、减小总次数了。
那么接下来要作的是,肯定环上每条边的移动方向和次数。
设环长为\(n\),\(A_i\)为以\(i\)为根求得的\(a\)值,\(x_i\)为环上第\(i\)条边的移动参数(正负表示方向,绝对值表示次数)
由于进出平衡,咱们能够获得一个方程组
\[\begin{cases}x_1-x_2=A_1\\x_2-x_3=A_2\\...\\x_n-x_1=A_n\end{cases}\]
这时候咱们发现上面那个方程组是没有惟一解的。那到底该分摊多少呢?
不着急,咱们着眼于最小化\(\sum|x_i|\),再把方程组整一整
\[\begin{cases}x_1=x_1-0\\x_2=x_1-A_1\\x_3=x_1-A_1-A_2\\...\\x_n=x_1-\sum_{i=1}^{n-1}A_i\end{cases}\]
每一行的\(A\)都是前缀和的形式,在树形DP的时候对应的是环上的每一个点的\(a\)。
看到这儿就豁然开朗了。这不等于说,数轴上有若干个点取值分别为每个\(a\),找到一个点\(x_1\)使它到全部点距离之和最小?
把全部\(a\)排序,\(x_1\)不就能够取第\(\frac n 2\)大的数到第\(\frac n 2+1\)大的数之间的任意整数么?
最后把环上的DP值改一改就OK了。
由于不是二分图了,因此直接解方程好像作不下去。咱们从更直观的意义来理解它。
仍然是去掉一条边作树形DP。这时候咱们看看,在这条非树边上操做,在奇偶染色模型下等价于什么呢?
没错,两个棋子会在这里同时变成空位,两个空位会在这里同时变成棋子!
那这条边存在的意义,是把棋子或空位中多的减掉、少的补上。要操做多少次呢?\(\frac{\sum A_i}{2}\),在树形DP中对应的是\(\frac{a_{rt}}{2}\)。
注意若是\(a_{rt}\)是奇数那么无解。不然环底部的\(A\)发生了变化,致使要把环上的\(a\)都减掉\(\frac{a_{rt}}{2}\)。
最后的最后扫一遍数组统计答案。代码没有任何难点。
#include<bits/stdc++.h> #define R register int #define G if(++ip==ie)if(fread(ip=buf,1,SZ,stdin)) using namespace std; const int SZ=1<<19,N=1e5+9,M=2*N; char buf[SZ],*ie=buf+SZ,*ip=ie-1; inline int in(){ G;while(*ip<'-')G; R x=*ip&15;G; while(*ip>'-'){x*=10;x+=*ip&15;G;} return x; } int he[N],ne[M],to[M],f[N],a[N],b[N]; int Getf(R x){ return x==f[x]?x:f[x]=Getf(f[x]); } void Dfs(R x,R op){ a[x]=op; for(R y,i=he[x];i;i=ne[i]) if((y=to[i])!=f[x]) f[y]=x,Dfs(y,-op),a[x]+=a[y]; } int main(){ R n=in(),m=in(),rt=1,re=1,p=0,ans=0; for(R i=1;i<=n;++i)f[i]=i; for(R p=0,i=1;i<=m;++i){ R x=in(),y=in(); if(Getf(x)!=Getf(y))f[f[x]]=f[y]; else{rt=x,re=y;continue;}//把多的边单独拿出来 ne[++p]=he[x];to[he[x]=p]=y; ne[++p]=he[y];to[he[y]=p]=x; } f[rt]=0;Dfs(rt,1); if(n==m){//基环树 for(R x=re;x;x=f[x])b[++p]=a[x]; if(p&1){//奇环 if(a[rt]&1)return puts("-1"),0; for(R x=re;x;x=f[x])a[x]-=a[rt]>>1; } else{//偶环 if(a[rt])return puts("-1"),0; sort(b+1,b+p+1); for(R x=re;x;x=f[x])a[x]-=b[p>>1]; } }//树 else if(a[rt])return puts("-1"),0; for(R i=1;i<=n;++i)ans+=abs(a[i]); return cout<<ans<<endl,0; }