zoukankan      html  css  js  c++  java
  • [AGC004F] Namori

    一、题目

    点此看题

    二、解法

    精妙的转化,我只是想说这种东西怎么可能训练得来嘛,这完全就是靠天赋啊...

    先从这种简单的情况入手,考虑间隔染色,我们把奇数深度的点染成黑色,偶数深度的点染成红色,那么问题转化成了把所有原来为黑的点变成红色,原来为红的点变成黑色,每次可以交换两个点的颜色。

    考虑构造答案下界,设某个子树有 (a) 个黑点、(b) 个红点,因为最终状态是 (b) 个黑点、(a) 个红点,那么父边至少交换 (|a-b|) 次。又因为每次一定能找到合法的交换点对(留读者自证),所以答案下界可以取到。

    那么把黑点权值设为 (1),红点权值设为 (-1),设 (a_i) 表示 (i) 子树内的权值和,答案是 (sum |a_i|)

    偶环树

    受到树讨论的启发,考虑权值的变化即可。

    img

    多出来这条边的作用其实就是把权值输送到另一个子树去,设输送的权值为 (x),那么左边的点权值就会增加 (x),右边的点权值就会减少 (x),记 (k_i)(-1/1) 表示这个点的权值是增加还是减少,那么重新计算环上的交换次数是:

    [sum|k_icdot a_i-x|+|x| ]

    最小化这个式子直接取中位数即可。

    奇环树

    注意此种情况特殊边的作用就不是交换两个点的颜色了,而是把两个点的颜色直接反转(如果都是黑那么变成都是红、如果都是红那么变成都是黑),所以此种情况 (a_1) 是偶数既有解。

    那么此条边使用的下界任然是 (frac{a_1}{2}),还是在适当时机交换即可(读者自证),考虑这条边的影响之后当树做就行了。

    三、总结

    本题运用的转化:同色操作转异色操作,可以去掉同色操作的限制。

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    const int M = 100005;
    #define ll long long
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,k,sum,tot,f[M],a[M],w[M],st[M],A,B;ll ans;
    struct edge
    {
    	int v,next;
    }e[2*M];
    int Abs(int x)
    {
    	return x>0?x:-x;
    }
    void dfs(int u,int fa)
    {
    	sum+=w[u];
    	for(int i=f[u];i;i=e[i].next)
    	{
    		int v=e[i].v;
    		if(w[v] && v^fa)
    			A=u,B=v;
    		if(!w[v])
    			w[v]=-w[u],dfs(v,u);
    	}
    }
    void cal(int u,int fa)
    {
    	for(int i=f[u];i;i=e[i].next)
    	{
    		int v=e[i].v;
    		if(v==fa || (u==A && v==B) || (u==B && v==A))
    			continue;
    		cal(v,u);
    		a[u]+=a[v];w[u]+=w[v];
    	}
    }
    int main()
    {
    	n=read();m=read();
    	for(int i=1;i<=m;i++)
    	{
    		int u=read(),v=read();
    		e[++tot]=edge{v,f[u]},f[u]=tot;
    		e[++tot]=edge{u,f[v]},f[v]=tot;
    	}
    	w[1]=1;dfs(1,0);
    	if(sum%2) {puts("-1");return 0;}
    	if(A)
    	{
    		if(w[A]==w[B])//odd circle
    		{
    			ans+=Abs(sum)/2;
    			w[A]-=sum/2;w[B]-=sum/2;
    			sum=0;
    		}
    		else
    			a[A]++,a[B]--;
    	}
    	if(sum) {puts("-1");return 0;}
    	cal(1,0);st[++k]=0;
    	for(int i=1;i<=n;i++)
    	{
    		if(a[i]) st[++k]=a[i]*w[i];
    		else if(w[i]) ans+=Abs(w[i]);
    	}
    	sort(st+1,st+1+k);
    	int x=st[(k+1)/2];
    	for(int i=1;i<=k;i++)
    		ans+=Abs(st[i]-x);
    	printf("%lld
    ",ans);
    }
    
  • 相关阅读:
    求全排列,调用C++函数
    ZOJ 3508 (the war)
    HDU 1285
    SDUT--枚举(删数问题)
    SDUT--进制转换
    位运算
    [NOI2015]软件包管理器
    列队[noip2017]
    [APIO2007]动物园
    [NOI2001]炮兵阵地
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15257626.html
Copyright © 2011-2022 走看看