zoukankan      html  css  js  c++  java
  • [CSP-S2019]树上的数 题解

    CSP-S2 2019 D1T3

    考场上写了2h还是爆零……思维题还是写不来啊


    思路分析

    最开始可以想到最简单的贪心,从小到大枚举每个数字将其移动到最小的节点。但是通过分析样例后可以发现,一个数字在移动的过程中也可能有无关的边的删除,很难处理。显然直接贪心是不可能的。

    分析删边对图的影响。可以发现,一条边删去之后,边两端的部分将不会产生任何影响。也就是说,两边的关系只有这一条边。于是还是之前那个贪心的想法,将边的问题转化为点的问题。现在来分析怎么求解。

    具体实现

    分析样例后可以发现以下性质:

    1. 若某数字要从某个节点的某条边离开,那么这条边一定是这个节点最先被选择的边
    2. 某数字要从某个节点的x边进入,从y边离开,那么x一定先于y被选择,并且这个节点的其它边不在x和y之间被选择
    3. 某数字要最终停在某个节点,那么这个数字通往这个节点的边一定是这个节点最后被选择的边

    因此,对于每个节点,关于与其相连的边,有以下三种约束条件:

    1. 某条边最先被选择
    2. 某条边紧跟着另一条边被选择
    3. 某条边最后被选择

    我们可以根据上面提到的性质,通过判断每个节点的约束条件是否有冲突来求解方案。

    可以发现,对于每个节点,其所有出边构成一条偏序链,我们可以通过维护一个链表来保证约束条件没有冲突。接下来分析一个数字经过一条边会产生什么冲突。假设某数字从$x$到$y$经过了边$(u,v)$。

    1.若$x=u$,即$(u,v)$为路径的起始边,$u$为路径的起点
    • $(u,v)$已经被其它数字沿相同方向走过,显然不能再走,不合法
    • $u$上的原数字已走出去,显然不能再走,不合法
    • 已经有数字搬运到$u$,即加上这条边后$u$的边将构成一条完整的偏序链,此时若有其它边不在这条偏序链上,因为要删去所有的边,不合法
    2.若$v=y$,即$(u,v)$为路径的终边,$v$为路径的终点
    • $(u,v)$已经被其它数字沿相同方向走过,显然不能再走,不合法
    • $v$已有数字走入,显然不能再走,不合法
    • 已经有数字从$v$搬运出去,即加上这条边后$v$的边将构成一条完整的偏序链,此时若有其它边不在这条偏序链上,因为要删去所有的边,不合法
    3.若$x!=u$且$v!=y$,即$(u,v)$为路径的中间部分
    • $(u,v)$已经被其它数字沿相同方向走过,显然不能再走,不合法
    • 加上这条边后$v$的边将构成一条完整的偏序链,此时若有其它边不在这条偏序链上,因为要删去所有的边,不合法
    • 加上这条边后构成的偏序链成为一个环,不合法

    偏序链可以用链表O(1)维护。另外,可以发现,每次数字的转移要维护的是一些连续的节点的关系,因此可以用一遍dfsO(n)维护。加上枚举数字的O(n),总的时间复杂度O(n)。

    细节比较多,注意不要漏点错点。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int N=2e3+100;
    int T,n,tot;
    int head[N],ver[2*N],Next[2*N];
    int rev[N],pre[N],cnt1[N],cnt2[N],cnt3[N],from[N],to[N],p[N][N],header[N][N],tailer[N][N];
    bool pd[N];
    void add(int x,int y)
    {
        ver[++tot]=y,Next[tot]=head[x],head[x]=tot;
        ver[++tot]=x,Next[tot]=head[y],head[y]=tot;
    }
    void check(int x,int root)//root就是数字的起点
    {
        for(int i=head[x],y=ver[i];i;i=Next[i],y=ver[i])
            if(y!=pre[x])
            {
                pre[y]=x,pd[y]=1;
                if(x!=root)
                {
                    if(p[x][y]==x || !p[x][y])
                        pd[y]=0;
                    if(tailer[x][y]==from[x] && header[x][pre[x]]==to[x] && cnt1[x]*2+cnt2[x]+cnt3[x]-2>0)
                        pd[y]=0;
                    if(tailer[x][y]==pre[x])
                        pd[y]=0;
                }//中间边
                else
                {
                    if(p[x][y]==x || !p[x][y])
                        pd[y]=0;
                    if(from[x] && tailer[x][y]==from[x] && cnt1[x]*2+cnt2[x]+cnt3[x]-1>0)
                        pd[y]=0;
                }//起始边
                pd[y]&=pd[x];//x不行y也不行
                check(y,root);
            }
        if(x==root)
            pd[x]=0;//起终点相同也不行
        else
            if(from[x] ||(to[x] && tailer[x][to[x]]==pre[x] && cnt1[x]*2+cnt2[x]+cnt3[x]-1>0))
                pd[x]=0;//x作为终点
    }
    void clear()
    {
        memset(head,0,sizeof(head));
        memset(Next,0,sizeof(Next));
        memset(from,0,sizeof(from));//该节点上的数字从哪条边进来
        memset(to,0,sizeof(to));//该节点上的数字从哪条边出去
        memset(cnt1,0,sizeof(cnt1));//该节点还剩下几条双向没走过的边
        memset(cnt2,0,sizeof(cnt2));//该节点还剩下几条单向出边
        memset(cnt3,0,sizeof(cnt3));//该节点还剩下几条双向出边
        memset(pd,0,sizeof(pd));//可行性
        memset(p,0,sizeof(p));//-1表示没走过,0表示双向都走过,x表示以x为起点单向走过这条边,这里的初始化好像没什么用
        memset(header,0,sizeof(header));//偏序链起始边
        memset(tailer,0,sizeof(tailer));//偏序链终边
        tot=0;
    }
    int main()
    {
        scanf("%d",&T);
        while(T--)
        {
            clear();
            scanf("%d",&n);
            for(int i=1;i<=n;i++)
                scanf("%d",&rev[i]);
            for(int i=1,x,y;i<n;i++)
            {
                scanf("%d%d",&x,&y);
                add(x,y);
                cnt1[x]++,cnt1[y]++;
                p[x][y]=p[y][x]=-1;
                header[x][y]=tailer[x][y]=y,header[y][x]=tailer[y][x]=x;//初始值
            }
            for(int i=1,now;i<=n;i++)
            {
                for(int j=1;j<=n;j++)
                    pre[j]=0;
                pd[rev[i]]=1;//初始值
                check(rev[i],rev[i]);//dfs判断可行性
                for(int j=1;j<=n;j++)
                    if(pd[j])
                    {
                        now=j;
                        break;
                    }//找到字典序最小的可行终点
                printf("%d ",now);
                from[now]=pre[now];
                while(pre[now]!=rev[i])
                {
                    if(p[pre[now]][now]==-1)
                    {
                        p[pre[now]][now]=p[now][pre[now]]=pre[now];
                        cnt1[now]--,cnt1[pre[now]]--,cnt3[now]++,cnt2[pre[now]]++;
                    }//双向没走过
                    else
                    {
                        p[pre[now]][now]=p[now][pre[now]]=0;
                        cnt2[now]--,cnt3[pre[now]]--;
                    }//反向走过
                    header[pre[now]][tailer[pre[now]][now]]=header[pre[now]][pre[pre[now]]];
                    tailer[pre[now]][header[pre[now]][pre[pre[now]]]]=tailer[pre[now]][now];//链表插入
                    now=pre[now];
                }
                if(p[pre[now]][now]==-1)
                {
                    p[pre[now]][now]=p[now][pre[now]]=pre[now];
                    cnt1[now]--,cnt1[rev[i]]--,cnt3[now]++,cnt2[rev[i]]++;
                }
                else
                {
                    p[pre[now]][now]=p[now][pre[now]]=0;
                    cnt2[now]--,cnt3[rev[i]]--;
                }
                to[rev[i]]=now;
            }
            puts("");
        }
    }
  • 相关阅读:
    SecureCRT 连接 虚拟机Linux 命令
    如何使用secureCRT连接vmware中的虚拟主机?
    SecureCRT8.1+SecureCRT_keygen完成注册
    常用python机器学习库总结
    Torch7在Ubuntu下的安装与配置
    朴素贝叶斯算法 & 应用实例
    编写MR代码中,JAVA注意事项
    march.
    Docker CentOS 7.2镜像systemd问题解决办法
    Docker 基础命令 简要入门
  • 原文地址:https://www.cnblogs.com/TEoS/p/11969712.html
Copyright © 2011-2022 走看看