【AtCoder】【思维】【二分图】【模型转化】Namori(AGC004)

11 篇文章 0 订阅
6 篇文章 0 订阅

参考:
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],也是原题中这条边需要操作的次数。那么答案就是 ∑ s u m [ i ] \sum sum[i] sum[i],sum[]表示这个子树中的 ∑ ( + 1 ) + ∑ ( − 1 ) \sum (+1)+\sum (-1) (+1)+(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,然后就可以删去这条边就可以是树的情况了。
这个时候答案就是:
A n s = m i n ( a b s ( x ) + ∑ i = 1 N a b s ( s u m [ i ] + k i x ) ) Ans=min(abs(x)+\sum_{i=1}^{N}abs(sum[i]+k_ix)) Ans=min(abs(x)+i=1Nabs(sum[i]+kix))(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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值