參考:
Namori[agc-004F]-by ezhjw
editotial-AGC004
AGC004F-by 楊耀良
題意:
給你一棵樹或者是基環樹,每個節點可以爲白色或者是黑色。你可以將相鄰的,具有相同顏色的兩個點同時反轉顏色。初始的時候所有的節點都是白色的,你需要花費最少的步數來讓所有的節點都變成黑色。如果無法達到輸出-1。
數據範圍:
1<=N<=10^5,N-1<=M<=N
思路:
首先可以想到的是這道題需要分類討論。
首先是樹的情況,因爲樹是一個二分圖,所以說如果我們將深度爲1,3,5…的節點看成是S集合,深度爲2,4,6…的節點看成是T集合,那麼就只能夠是S集合中的元素與T集合中的元素進行同時反轉。這樣我們就可以再轉換一下,假設S集合中的節點都是+1,並且假設有一個硬幣,T集合中的節點都是-1,並且假設爲空位,然後依次反轉顏色就相當於是將+1和-1交換位置,也就是把硬幣放到一個空位上去,然後目標狀態就是將所有原來在S集合的點都變爲T集合中的點,原來所有在T集合的點都變爲S集合中的點,也就是所有的空位都被放上了硬幣,並且原來有硬幣的位置都變爲了空位。如果說全局的-1和+1的數量不相同的時候,那麼就是無解的狀態。
然後我們來觀察某棵子樹,那麼它也應該滿足-1和+1的數量相同。但實際情況中可能是不相同的,那麼就需要從外界引入硬幣,或者是輸出硬幣。考慮子樹的根節點爲x,x的father爲fa,那麼就可以從x->fa的這條路徑引入或者是輸出硬幣,而操作的次數正好是這個子樹中+1的數目減去-1的數目的絕對值,也就是sum[x],也是原題中這條邊需要操作的次數。那麼答案就是,sum[]表示這個子樹中的,那麼是一棵樹的情況就解決了。
接下來是N=M的情況。
因爲和二分圖有關,所以說需要根據是奇環還是偶環進行分類討論。
當是奇環的時候:
選擇環上的一條邊,那麼刪除這條邊之後就是一棵樹的情況了,那麼我們就可以考慮這一條邊所帶來的影響,然後刪掉它,在剩餘的樹裏面繼續討論就可以了。因爲是一個奇環,所以說這條邊所連接的兩個點可以認爲就是出現矛盾的那個地方,那麼這兩個點就屬於同一個集合。那麼就可以在這兩個點上面同時放上硬幣或者是同時移除硬幣。那麼就可以將所有的矛盾的都轉移到這條邊上來,讓它來消除矛盾(例如,如果硬幣過多,就可以移過來,然後刪掉,說過硬幣少了,就可以在這裏產生硬幣,然後運出去)。我們發現,這條邊能夠讓+1與-1的數目差同時-2,那麼也就是說+1和-1的奇偶性相同的時候纔是有解的,否則輸出-1。然後,我們可以想成是這條邊預先操作了固定的次數(就是abs(num[+1]-num[-1])/2),然後將對於兩邊的點的貢獻先算出來,然後刪去這條邊,就可以歸到樹的情況了。
上面說的奇環上面的那條邊能夠做到同時+1或者是-1的問題,其實就是讓硬幣憑空消失或者是憑空出現。因爲這兩個點屬於同一層,那麼如果說這兩個點都含有硬幣,那麼將這條邊反轉之後,這2個硬幣都消失了。或者這樣來解釋:所有的點到這兩個點的路徑長度都相等,初始的時候這兩個點上的顏色肯定是相同的,那麼就可以讓其中1個點沿着環運出去一種顏色,這時兩個點的顏色就不同了。在下一次需求這種顏色時,就從另一個點運出去,那麼這樣子兩個點的顏色就又相同了,那麼就再反轉一次這條邊,變回了初始的狀態。
當是偶環的時候:
這個時候還是一個二分圖,所以說+1的數目和-1的數目仍然需要相等纔會有解。然後仍然是選擇一條邊,然後考慮這條邊操作了多少次。假設操作了x次(從u->v),那麼它所造成的影響就是u,v(這條邊的兩端),u->lca(u,v)上所有點都-x,v->lca(u,v)上面所有點都+x,然後就可以刪去這條邊就可以是樹的情況了。
這個時候答案就是:
(x可以是負數)
ki可以是+1,-1,或者是0。0的話,就是在u->v這條路徑之外的點,就可以直接按照樹的算法來做;1的話就是v->lca(u,v)的情況;-1的話就是u->lca(u,v)的情況。然後就可以讓k[u]=-1,k[v]=1,然後做一遍DFS將ki算出來,就可以做了。然後看上面的式子,可以發現其實是(-k)*sum[i]到x的距離的加和,而abs(x)就是0到x的距離。那麼根據初中的知識,最優的一定是選擇{(-k)*sum[]+0}中的中位數。
代碼:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define MAXN 100000
using namespace std;
typedef long long LL;
struct node
{
int to;
node *nxt;
}edges[MAXN*2+5];
node *ncnt,*Adj[MAXN+5];
int N,M,spx,spy;
LL sum[MAXN+5];
LL cirlen,dist[MAXN+5],xs[MAXN+5];
LL stk[MAXN+5],t;
bool vis[MAXN+5];
void Init()
{
ncnt=&edges[0];
}
void AddEdge(int u,int v)
{
node *p=++ncnt;
p->to=v;
p->nxt=Adj[u];
Adj[u]=p;
node *q=++ncnt;
q->to=u;
q->nxt=Adj[v];
Adj[v]=q;
}
void DFS(int u,int fa)
{
vis[u]=true;
for(node *p=Adj[u];p!=NULL;p=p->nxt)
{
int v=p->to;
if(v==fa)
continue;
if(vis[v]==true)
{
spx=u,spy=v;
cirlen=dist[u]-dist[v]+1;//之前莫名wa是因爲這裏u和v寫反了...
continue;
}
dist[v]=dist[u]+1;
DFS(v,u);
}
}
void GetXs(int u,int fa)
{
vis[u]=true;
for(node *p=Adj[u];p!=NULL;p=p->nxt)
{
int v=p->to;
if(v==fa||vis[v]==true)//避免訪問到之前找到的那一條特殊的邊
continue;
GetXs(v,u);
xs[u]+=xs[v];
sum[u]+=sum[v];
}
}
int main()
{
// freopen("print.in","r",stdin);
// freopen("print.out","w",stdout);
Init();
scanf("%d %d",&N,&M);
int u,v;
for(int i=1;i<=M;i++)
{
scanf("%d %d",&u,&v);
AddEdge(u,v);
}
dist[1]=0;
DFS(1,-1);//計算深度/環的長度
LL ans=0,tot=0;
for(int i=1;i<=N;i++)
{
if(dist[i]%2==0)
sum[i]=-1LL;
else
sum[i]=1LL;
tot+=sum[i];
}
if(N-1==M)//處理樹
{
if(tot!=0)
{
printf("-1\n");
return 0;
}
}
else
{
if(cirlen%2==1)//處理奇環
{
if(tot%2!=0)
{
printf("-1\n");
return 0;
}
sum[spx]-=tot/2;
sum[spy]-=tot/2;
ans+=abs(tot)/2;
}
else//處理偶環
{
if(tot!=0)
{
printf("-1\n");
return 0;
}
xs[spx]=1LL;
xs[spy]=-1LL;
}
}
memset(vis,0,sizeof(vis));
GetXs(1,-1);//計算係數
for(int i=1;i<=N;i++)
{
if(xs[i]==0) ans+=abs(sum[i]);
else
{
if(xs[i]==1)
sum[i]=-sum[i];//乘上係數
stk[++t]=sum[i];
}
}
stk[++t]=0;
sort(stk+1,stk+1+t);
LL k=stk[(t+1)/2];
for(int i=1;i<=t;i++)
ans+=1LL*abs(stk[i]-k);
printf("%lld\n",ans);
return 0;
}