[AGC004_f Namori] [问题转化+贪心]

[题目大意]

有一个连通图,由N个顶点和M条边构成,没有重边和自环,且N-1<=M<=N,点依次标为1..N号,第i条边连接点ai和bi。

起初所有顶点都是白色。你每次操作可以将两个相邻的同色点的颜色取反(白->黑,黑->白)。问最少多少次可以将点都变成黑色,如果无解,输出-1。

[思路]

这题真是神题啊,给跪了……

首先,每次操作都要改两个点,看起来就很麻烦,所以必须尽量转化成更简单的问题。

由于树是一个二分图,所以我们可以将所有点U,V相间地染色。然后U类点如果是白色,令它的权值为1,如果是黑色,令它的权值为0;V类点如果是白色,令它的权值为0,如果是黑色,令它的权值为1。这样,每一次操作相当于将树上的一对相邻的1和0交换了一下,而我们的目的就是让最后所有的1都位于V类点。

[树的情况]

对于树的情况,这就比较简单了,也属于一个经典问题。

(1)如果1的个数不等于0的个数,显然无解;

(2)否则,我们令白色点权值为-1,黑色点权值为1,以x为根的子树权值和为s[x],那么对于每个x,如果s[x]>0,显然至少要有s[x]个1要搬出去;如果s[x]<0,至少要有|s[x]|个1搬进来。所以答案至少为\sum abs(s[i]),再观察一下,你就会发现,答案就是这个,因为我们从下到上推一遍,就可以利用这些必要的步数达到目的。

那么对于环怎么办呢?

我们肯定要分奇环和偶环讨论了:

[奇环]

奇环上面可以找到一对相邻的点属于同类点(u,v),这就是矛盾所在。如果所有操作不涉及这条边,都跟树做法一样,如果涉及呢?对(u,v)进行一次操作,相当于将两个1全部变为0,或者将两个0全部变为1,也就是说我们可以两个两个的制造或者消灭1。可不可以让问题更简单?我们可以刻意地选择其中一种U-V染色方式,使得一开始1的个数<=0的个数,这样1就成了稀缺资源,我们显然没有必要消除1,这样只要考虑制造1的操作。

(1)如果1比0少k个,k是奇数,显然问题无解。

(2)否则可以制造k/2次1,全部堆放在u和v上(实际上不可以堆放,但迟早要全部搬出去,可以假设都堆放在这里),然后忽略(u,v)这条边,接下来和树的做法完全相同!!

[偶环]

偶环仍然是个二分图,所以1和0的个数是守恒的,只能移动1,不能消灭或生成1,所以比奇环简单。但有个问题,那就是环没办法推出最少步数。我们可以在环上强制找一条边(u,v),设这条边从u运了x个1到v,这样这条边也可以不考虑了,以u为根后,仍然和树的做法一样,这样得到的答案就是\sum abs(s[i]+kx),其中k∈{0,1}(不是v的祖先的点没有影响,k=0;v的祖先(不需要考虑u,因为s[u]恒为0)的k=1)。我们只要找到最合适的x,使得答案最小就可以了。k=0的那些项都可以直接求出来;剩下的k=1,观察发现其几何意义是在数轴上找一点x,使得它到一些点的距离和最小,我们贪心地选取中位数作为x即可。

#include <cstdio>
#include <algorithm>
#define rep(i,j,k) for (i=j;i<=k;i++)
#define edge(j) for (j=fst[x];j;j=nxt[j])
using namespace std;
const int N=1e5+5;
int n,m,i,j,u,v,from,trans;
int odd,even,ans,delta;
int pn,p[N],s[N],bw[2],cnt[2];
int En,fst[N],vis[N],col[N],nxt[N*2],to[N*2],have[N];
void add(int u,int v) {
	En++; nxt[En]=fst[u]; fst[u]=En; to[En]=v;
}
void dfs(int x,int fa,int flg)
{
	int j,v;
	vis[x]=1; col[x]=flg; cnt[flg]++;
	edge(j)
	{
		v=to[j];
		if (v==fa) continue;
		if (vis[v]) {
			trans=v; from=x;
			if (col[x]==col[v]) odd=1;
			else even=1;
			continue;
		}
		dfs(to[j],x,flg^1);
	}
}
void dfs2(int x,int fa)
{
	int j,v;
	if (trans==x && even) have[x]=1;
	edge(j)
	{
		v=to[j];
		if (x==from && v==trans) continue;
		if (x==trans && v==from) continue;
		if (v==fa) continue;
		dfs2(v,x);
		if (have[v]) have[x]=1;
		s[x]+=s[v];
	}
	if (x!=from) {
		if (have[x]) p[++pn]=s[x];
		else ans+=abs(s[x]);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	rep(i,1,m)
	{
		scanf("%d%d",&u,&v);
		add(u,v); add(v,u);
	}
	dfs(1,1,0);
	bw[0]=0; bw[1]=1;
	if (cnt[0]<cnt[1]) swap(bw[0],bw[1]); //to ensure num[black]<=num[white]
	rep(i,1,n) {
		col[i]=bw[col[i]];
		if (col[i]) s[i]=1;
		else s[i]=-1;
	}
	
	if (odd) {
		delta=abs(cnt[0]-cnt[1]);
		if (delta%2) { printf("-1\n"); return 0; }
		ans+=delta/2;
		s[trans]+=delta/2;
	}
	else if (cnt[0]!=cnt[1]) { printf("-1\n"); return 0; }
	
	if (!from) from=1;
	dfs2(from,from);
	
	if (even) {
		p[++pn]=0; //abs(x) should be counted 
		sort(p+1,p+1+pn);
		rep(i,1,pn) ans+=abs(p[i]-p[pn/2+1]);
	}
	printf("%d\n",ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Orzage/article/details/84522350