[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搬進來。所以答案至少為,再觀察一下,你就會發現,答案就是這個,因為我們從下到上推一遍,就可以利用這些必要的步數達到目的。
那麼對於環怎麼辦呢?
我們肯定要分奇環和偶環討論了:
[奇環]
奇環上面可以找到一對相鄰的點屬於同類點(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為根後,仍然和樹的做法一樣,這樣得到的答案就是,其中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;
}