[agc018f]Two Trees——神仙构造题+欧拉回路or黑白染色

本文详细解析了 AGC018F 的解题思路,介绍了一种利用欧拉回路性质的解法,确保每一点的子树和为-1或1。通过构造新图并求其欧拉回路,对奇点进行权值赋值,证明了算法的正确性。

题目大意:

给定两颗带了标号的有根树,大小都是n,现在让你对每一个点赋一个权值,使得每一个点的子树和为-1或1。

思路:

首先我们可以算出每一个点的权值的奇偶性,一个点如果在两颗树中奇偶性不一样一定无解,反之一定有解。
考虑怎么构造,对于一个偶点,我们直接赋为0,对于奇点我们赋上-1或1。
它的核心思想是利用欧拉回路的性质使得子树中的点尽量1和-1配对。
建边方式如下:

  1. 两棵树按照原来的边分别建立。
  2. 如果点u为奇点,即有偶数颗子树,在两棵树的对应点上面连边。
  3. 两棵树的根之间连一条边。

不难发现这个新图的度数都为偶数,于是对于这个新图求欧拉回路,对于奇点,如果它的附加边是从A->B,那么我们赋权值为1,反之为-1。
下面证明算法的正确性:
对于每一个奇点,我们在欧拉回路中计算它的权值当且经当这条路径通过这个点跨越了一棵树。

  1. 对于一个点u,一条从u出发并且又回到u的路径上的点集的点权和必定为0.(必定是走出去一次,走回来一次)
  2. 对于一个点u,一条从u的子树出发并且从u的子树回来的路径,其中经过的u的子树中的点集的权值和为0.(即使走出了子树,那必定是走到另外一棵树上,那必定是走出去一次,走回来一次)
  3. 对于一个点u,一条从u的子树出发并且不从u的子树回来的路径,子树中的权值和必定是1或-1.
  4. 于是对于一个偶点,它将有若干条2路径和1条3路径,自身权值为0,所以子树和为1或-1.
  5. 对于一个奇点,它的子树和(不包括它本身)可能为0(若干条起点终点都在子树内和一条起点终点都在子树外)或者+2,-2(若干条起点终点都在子树内和两条3路径),加入它本身之后,权值和一定为+1或者-1。

于是证明完毕。
这道题还有更简单的黑白染色方法,方法类似,这里不再赘述。

#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
typedef long long ll;

using namespace std;

void File(){
    freopen("agc018f.in","r",stdin);
    freopen("agc018f.out","w",stdout);
}

template<typename T>void read(T &_){
    T __=0,mul=1; char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-')mul=-1;
        ch=getchar();
    }
    while(isdigit(ch))__=(__<<1)+(__<<3)+(ch^'0'),ch=getchar();
    _=__*mul;
}

const int maxn=3e5+10;
int n,f[maxn],g[maxn],sf[maxn],sg[maxn],rf,rg;
int beg[maxn],las[maxn<<1],to[maxn<<1],cnte=1;
int euler[maxn],cnt,ans[maxn];
bool vis[maxn<<1];

void add_edge(int u,int v){
    las[++cnte]=beg[u]; beg[u]=cnte; to[cnte]=v;
    las[++cnte]=beg[v]; beg[v]=cnte; to[cnte]=u;
}

int s[maxn],tp;
void dfs(int u){
    s[++tp]=u;
    for(int &i=beg[u];i;i=las[i]){
        if(vis[i/2])continue;
        vis[i/2]=1;
        dfs(to[i]);
    }
    euler[++cnt]=u;
}

int main(){
    File();
    read(n);
    REP(i,1,n){
        read(f[i]);
        if(f[i]>0){
            ++sf[f[i]];
            add_edge(f[i],i);
        }
        else rf=i;
    }
    REP(i,1,n){
        read(g[i]);
        g[i]+=n;
        if(g[i]>n){
            ++sg[g[i]];
            add_edge(g[i],i+n);
        }
        else rg=i+n;
    }

    REP(i,1,n)if(sf[i]%2!=sg[i+n]%2)
        return puts("IMPOSSIBLE"),0;

    add_edge(rf,rg);
    REP(i,1,n)if(sf[i]%2==0)
        add_edge(i,i+n);

    dfs(1);
    //REP(i,1,cnt)cout<<euler[i]<<endl;
    //cout<<endl;
    REP(i,1,cnt-1)if(abs(euler[i]-euler[i+1])==n){
        int id=min(euler[i],euler[i+1]);
        if(sf[id]%2==1)continue;
        if(euler[i]<=n)ans[id]=1;
        else ans[id]=-1;
    }

    puts("POSSIBLE");
    REP(i,1,n)printf("%d ",ans[i]);
    return 0;
}

转载于:https://www.cnblogs.com/ylsoi/p/9845710.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值