AGC004_F Namori
阿新 • • 發佈:2018-12-25
題面翻譯
給你一棵樹/基環樹,初始所有點為白色。每次可以選擇相鄰的兩個同色點,使它們的顏色反轉(白->黑,黑->白)。問最終能否將所有點變成黑點,如能輸出最少操作次數,否則輸出-1。
思路
這題得分三種情況討論……
首先最簡單的是樹。
我們考慮把題面轉換一下。我們選一個點為根,然後所有深度為奇數/偶數的點上標+1,深度為偶數/奇數的點上標-1。每次操作將交換相鄰的+1與-1。問最少移動多少次可以使每個深度為偶數/奇數的點都有棋子,而每個深度為奇數/偶數的點上都沒有棋子。顯然這個這個轉換時沒有問題的,那麼如果深度為奇數的點個數等於為偶的,則可行。因為對於每次操作可以說是獨立的,所以我們想讓操作次數最少,使所有棋子移動距離最小,就要儘量讓所有棋子在子樹內移動。我們設\(S_x\)
然後我們考慮一下環的長度為奇數的基環樹。
我們考慮去掉環上的一條邊,這樣就變成了一棵樹,但是去掉的這條邊連線了兩個奇偶性相同的點,這意味著多出的這條邊可以在兩邊同時產生+1或-1。那麼如果整棵樹的點個數部位偶數意味著無解。同時我們只會在多出的邊兩端同時產生+1或-1,否則相互抵消就顯得多餘。那麼這樣是會產生還是刪掉並且變化量為多少就顯然了(根據整棵樹奇數點個數與偶數點個數的差值)。而且每次操作相互獨立,我們可以在多出的邊上先進行那麼多操作,再算答案。
最後是環的長度為偶數的基環樹。
同樣刪掉一條邊。但此時這條邊兩端奇偶相異。那麼這條邊的作用就如同“地下通道”,可以代替兩點間每條邊依次交換的操作。假設這條邊兩端點為x和y,lca為x和y的最近公共祖先,我們通過這條邊向y運輸了z個+1,那麼對於x到lca(不包括lca)這一路上的點的\(S[]\)
如果我們把y這一邊的\(S[]\)值都乘上-1,那麼貢獻就改為\((\sum |S_u-z|(u\in x->y))+z\)。我們把\(z\)看成\(|0-z|\),然後就變成經典的庫倉選址問題,直接取z=中位數即可。
#include<cmath> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define ll long long using namespace std; const int maxn=1e5; int n,m,tot=1,flag,key,cnt; int pre[maxn*2+8],now[maxn+8],son[maxn*2+8]; int dep[maxn+8],fa[maxn+8],f[maxn+8],v[maxn+8],tmp[maxn+8]; int read() { int x=0,f=1;char ch=getchar(); for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1; for (;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; return x*f; } void add(int u,int v) { pre[++tot]=now[u]; now[u]=tot; son[tot]=v; } void check(int x) { dep[x]=dep[fa[x]]+1; v[x]=(dep[x]&1)*2-1; f[x]=v[x]; for (int p=now[x];p;p=pre[p]) { int child=son[p]; if (fa[x]==child) continue; if (fa[child]) {flag=((dep[x]-dep[child]+1)&1)+1,key=p;continue;} fa[child]=x; check(child); f[x]+=f[child]; } } void dfs(int x) { f[x]=v[x]; for (int p=now[x];p;p=pre[p]) { int child=son[p]; if ((p|1)==(key|1)||fa[x]==child) continue; dfs(child); f[x]+=f[child]; } } int main() { n=read(),m=read(); for (int i=1;i<=m;i++) { int u=read(),v=read(); add(u,v),add(v,u); } fa[1]=1; check(1); if (!flag) { if (f[1]) { puts("-1"); return 0; } ll ans=0; for (int i=1;i<=n;i++) ans+=abs(f[i]); printf("%lld\n",ans); } if (flag==2) { if (f[1]&1) { puts("-1"); return 0; } //for (int i=1;i<=n;i++) printf("%d ",dep[i]);puts(""); //printf("check:%d %d %d\n",f[1],son[key],son[key^1]); //printf("%d %d\n",v[son[key]],v[son[key^1]]); v[son[key]]-=f[1]/2; v[son[key^1]]-=f[1]/2; ll ans=abs(f[1]/2); //printf("%d %d\n",v[son[key]],v[son[key^1]]); dfs(1); //printf("check:%d\n",f[1]); for (int i=1;i<=n;i++) ans+=abs(f[i]); printf("%lld\n",ans); } if (flag==1) { if (f[1]) { puts("-1"); return 0; } if (dep[son[key]]<dep[son[key^1]]) key^=1; int x=son[key]; tmp[++cnt]=0; while(x!=son[key^1]) tmp[++cnt]=f[x],x=fa[x]; sort(tmp+1,tmp+cnt+1); ll ans=tmp[(cnt+1)/2]; v[son[key]]-=ans; v[son[key^1]]+=ans; dfs(1); ans=abs(ans); for (int i=1;i<=n;i++) ans+=abs(f[i]); printf("%lld\n",ans); } return 0; }