【AtCoder】【思維】【二分圖】【模型轉化】Namori(AGC004)

參考:
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[i]\sum sum[i],sum[]表示這個子樹中的(+1)+(1)\sum (+1)+\sum (-1),那麼是一棵樹的情況就解決了。

接下來是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,然後就可以刪去這條邊就可以是樹的情況了。
這個時候答案就是:
Ans=min(abs(x)+i=1Nabs(sum[i]+kix))Ans=min(abs(x)+\sum_{i=1}^{N}abs(sum[i]+k_ix))(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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章