Model Transformation
如果不考虑到这个模型的转换,这道题可能很难入手:
题目要求如果相邻两个的颜色相同,就把它们都换成另一种颜色
我们先考虑树的情况,树是一个二分图,所以可以先黑白染色
我们考虑这样一个模型:
现在有一些池塘,池塘的连接结构和题中的图是一样的,刚开始有些池塘有水,有些池塘没有水(对应二分图)每次操作可以把一个池塘的水流到相邻的池塘里,要求最后原来有水的池塘没水,原来没水的池塘有水,求最少操作数
我们会发现这两个问题是等价的,最后池塘是否有水的状态全部改变,相当于每个池塘都被包含在了奇数次操作中,换在原题中就是所有的点的颜色都发生了改变,我们又发现相邻的两个池塘一个有水一个没水,就对应了原题中两个点颜色相同
NetWork Flow?
这个转换后的问题看上去简单了一些,我们先考虑树的情况
首先水在流动的过程中总量不变,所以如果刚开始有水的池塘数量不是 N2 N 2 的话答案肯定是-1
这样一个在池塘之间移水的问题让我想起了之前做过的一道TopCoder的题TheSquareDivOne,因为不同的池塘里的水是没有分别的,所以如果两个池塘的水撞到一起,可以由某一股水代替另一股水,换句话说,事实上水是可以穿过一个有水的池塘的
所以我们可以建立一个网络流的模型:超级源点向所有刚开始有水的池塘连流量是1费用是0的边,所有没水的池塘向超级汇点连流量是1费用是0的边,二分图左边和右边两两连流量是INF费用是它们在树上的距离的边,然后跑费用流
考虑到这样建图边数太多,可以按照原树的结构建图,这样是等价的
这个版本我写了一遍,TLE了
这个故事告诉我们不能什么时候都寄希望于网络流过1e5
Tree Case
这个网络流的思路是有可取之处的,我们至少明白了这个问题等价于一个匹配问题
我们考虑利用树的性质:如果某两个点可以在某棵子树内匹配掉,那么他们一定不会分别和子树外的点匹配
这样如果一棵子树的有水池塘数是X1,无水池塘数是X2,那么最后一定有且仅有 ∣X1−X2∣ ∣ X 1 − X 2 ∣ 个点没有被匹配
我们考虑对每条边算它被走了多少次,按照上一行的算法如果它的子树中有X个点没有被匹配,那么这些点必须和子树外的点匹配,所以这条边会被走X次
必要性显然,充分性根据树的性质可得
Odd Cycle Case
n=e的时候是一个基环外向树
如果是偶环,则原图仍是二分图,如果是奇环就不是
考虑这两个问题的思路是相似的:我们都希望算出环中一条边的贡献后拆掉这条边,这样就能套用树的算法了
我们先考虑奇环
我们考虑奇环里连接两个都有水(都没水)的池塘的那条边,看看走这条边意味着什么
这两条边的状态一起改变,相当于向两个池塘同时加水/减水
所以刚开始只要有水池塘数和无水池塘数奇偶性相同就有解
我们可以利用这两个池塘补水来使得两种池塘数量相同,然后把这条边拆掉,就能套用树上的解法了
Even Cycle Case
我们发现虽然这个图还是一个二分图但比奇环更不好做了
我们考虑拆対原图dfs之后的那条返祖边
我们要先考虑这条边走几次
我们渐渐发现这条边走的次数只会对环上的其他边产生影响,而且这个影响貌似是与这条边走的次数有关的一个式子,设这条边是
u→v
u
→
v
,走了
x
x
次(反过来走算负的),环上的其他边是,我们可以发现因为在u那里有x个未匹配的点通过
u→v
u
→
v
走掉了,所以
u,u1,u2...un
u
,
u
1
,
u
2
.
.
.
u
n
的所有点的未匹配个数都会相应的减少
x
x
,设我们对原图dfs之后的子树对应的未匹配个数分别是
c,c1,c2...cn
c
,
c
1
,
c
2
.
.
.
c
n
,则
u→v
u
→
v
走
x
x
次会使得环上的其它边的答案变成这样(这里再算上走这条边的代价
∣x∣
∣
x
∣
)
我们要求的是 minNx=−Nf(x) min x = − N N f ( x )
有的人的解法是:绝对值函数是一个下凸函数,所以若干个绝对值函数的和也是下凸函数,所以可以三分求最小值
不过事实上初中我们就学过若干个绝对值函数的和的最小值,考虑在数轴上的几何意义,最小值应该取在最中间的一段,所以把 0,c,c1,c2...cn 0 , c , c 1 , c 2 . . . c n 排个序和取中间的一个就是 x x <script type="math/tex" id="MathJax-Element-18">x</script>的值
Code
#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <utility>
#include <cctype>
#include <algorithm>
#include <bitset>
#include <set>
#include <map>
#include <vector>
#include <queue>
#include <deque>
#include <stack>
#include <cmath>
#define LL long long
#define LB long double
#define x first
#define y second
#define Pair pair<int,int>
#define pb push_back
#define pf push_front
#define mp make_pair
#define LOWBIT(x) x & (-x)
using namespace std;
const int MOD=1e9+7;
const LL LINF=2e16;
const int INF=1e9;
const int magic=348;
const double eps=1e-10;
const double pi=3.14159265;
inline int getint()
{
char ch;int res;bool f;
while (!isdigit(ch=getchar()) && ch!='-') {}
if (ch=='-') f=false,res=0; else f=true,res=ch-'0';
while (isdigit(ch=getchar())) res=res*10+ch-'0';
return f?res:-res;
}
int n,e;
vector<int> v[100048];
bool visited[100048];
int depth[100048],cnt[100048][2],fa[100048];
Pair cycle;
inline int myabs(int x) {return x>=0?x:-x;}
namespace tree
{
inline void dfs(int cur,int father)
{
int i,y;
cnt[cur][0]=cnt[cur][1]=0;cnt[cur][depth[cur]]=1;
for (i=0;i<int(v[cur].size());i++)
{
y=v[cur][i];
if (y!=father)
{
depth[y]=depth[cur]^1;dfs(y,cur);
cnt[cur][0]+=cnt[y][0];cnt[cur][1]+=cnt[y][1];
}
}
}
inline int solve()
{
int i,x,y;
depth[1]=1;dfs(1,-1);
if (cnt[1][0]!=cnt[1][1]) return -1;
int res=0;
for (i=2;i<=n;i++) res+=myabs(cnt[i][0]-cnt[i][1]);
return res;
}
}
inline void dfs(int cur,int father)
{
int i,y;visited[cur]=true;fa[cur]=father;
cnt[cur][0]=cnt[cur][1]=0;cnt[cur][depth[cur]]=1;
for (i=0;i<int(v[cur].size());i++)
{
y=v[cur][i];
if (y!=father)
{
if (visited[y]) {if (cycle.x==0) cycle=mp(y,cur);continue;}
depth[y]=depth[cur]^1;dfs(y,cur);
cnt[cur][0]+=cnt[y][0];cnt[cur][1]+=cnt[y][1];
}
}
}
namespace even
{
vector<int> cir;bool incir[100048];
inline int solve()
{
if (cnt[1][0]!=cnt[1][1]) return -1;
int cur,i,ans=0;cir.clear();
memset(incir,false,sizeof(incir));
for (cur=cycle.y;cur!=cycle.x;cur=fa[cur]) incir[cur]=true,cir.pb(cnt[cur][1]-cnt[cur][0]);
cir.pb(0);
sort(cir.begin(),cir.end());int nm=int(cir.size());
int coef=cir[nm/2-1];
for (i=0;i<int(cir.size());i++) ans+=myabs(cir[i]-coef);
for (i=2;i<=n;i++)
if (!incir[i]) ans+=myabs(cnt[i][1]-cnt[i][0]);
return ans;
}
}
namespace odd
{
int w[100048],sz[100048],sum[100048],ans;
inline void dfs(int cur,int father)
{
sz[cur]=(depth[cur]==0);sum[cur]=w[cur];int i,y;
for (i=0;i<int(v[cur].size());i++)
{
y=v[cur][i];
if (y==father || (y==cycle.x && cur==cycle.y) || (y==cycle.y && cur==cycle.x)) continue;
dfs(y,cur);sz[cur]+=sz[y];sum[cur]+=sum[y];
}
if (cur!=1) ans+=myabs(sum[cur]-sz[cur]);
}
inline int solve()
{
if (cnt[1][0]%2!=cnt[1][1]%2) return -1;
ans=myabs(cnt[1][0]-cnt[1][1])/2;int delta=(cnt[1][0]-cnt[1][1])/2,i;
for (i=1;i<=n;i++) w[i]=depth[i];
w[cycle.x]+=delta;w[cycle.y]+=delta;
dfs(1,-1);
return ans;
}
}
int main ()
{
int i,x,y;
n=getint();e=getint();
for (i=1;i<=e;i++) x=getint(),y=getint(),v[x].pb(y),v[y].pb(x);
if (e==n-1) {printf("%d\n",tree::solve());return 0;}
depth[1]=1;cycle=mp(0,0);dfs(1,-1);
if (depth[cycle.x]!=depth[cycle.y]) printf("%d\n",even::solve()); else printf("%d\n",odd::solve());
return 0;
}