1. 程式人生 > >[AGC004_f Namori] [問題轉化+貪心]

[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;
}