AtCoder "Grand Contest 041" E - Balancing Network (构造)


Grand Contest 041 - E - Balancing Network


题意

给定\(n\)条线以及\(m\)个平衡器,每条线自上向下按顺序排列,每个平衡器自左向右按顺序排列,并连接着两条线\(x,y\)

每条线最左边在刚开始都各有一个标记,每个标记都会向右走

对于每个标记,如果某时刻在第\(i\)条线,且该位置有个平衡器由第\(i\)条线指向第\(j\)条线,那么这个标记将会走到第\(j\)条线并继续向右走

相反,如果这个平衡器由其他线指向第\(i\)条线,则令牌不会受到这个平衡器的影响

试对两种子问题\((T)\)的不同条件来为每个平衡器确定一个方向(指向下或者指向上)

子问题一:最终所有标记走到一条线上

子问题二:最终所有标记不全在一条线上


限制

\(2\leq n\leq 50000\)

\(1\leq m\leq 100000\)

\(1\leq T\leq 2\)

\(1\leq x_i\lt y_i\leq N\)




思路

对于子问题一(最终所有标记走到一条线上)

首先我们需要知道初始时每条线上的标记最终可能走到的线是哪些,所以可以考虑逆序遍历所有平衡器,对于每个标记以 bitset<50010> 存储是否能走到某条线

初始时,设置 \(bitset[i][i]=1\) ,表示如果所有连接着第\(i\)条线的平衡器都是指向第\(i\)条线,最终第\(i\)条线上的标记还是会在第\(i\)条线上

逆序(从右向左)遍历平衡器,对于一个连接\(i,j\)的平衡器,发现

  • 如果让这个平衡器由\(i\)指向\(j\),那么\(i\)线上的标记就可以走到\(j\)线

  • 相反,\(j\)线上的标记就可以走到\(i\)线

所以考虑取或运算,以 bitset[i]|bitset[j]​ 作为两条线上的标记的状态即可

全部遍历完后,对所有\(n\)条线上的标记进行一次取与运算

对于最终得到的 \(bitset\) ,其中为\(1\)的位置就是所有标记可以共同走到的线

如果这个 \(bitset\) 全为0,说明无解

所以对最后这个 \(bitset\) 每一位再进行遍历,一旦发现某一位(第\(i\)位)为\(1\),则设置第\(i\)条线作为终点

设置\(vis\)数组进行访问标记,初始设置vis[i]=true,表示第\(i\)位此时可用

接下来重新逆序(从右向左)遍历平衡器,对于所有平衡器,让其指向此时已经标记过的点即可(类似于BFS)

例如对于连接\(x,y\)的平衡器,此时vis[x]=truevis[y]=false,说明逆序推导的过程中还没考虑到第\(y\)根线上的标记,所以我们只需要将第\(y\)根线上的标记引导至已经考虑到的第\(x\)根线上即可,指向就是\(x\rightarrow y\)

遍历完后所有平衡器就确定了状态,输出即可


对于子问题二(最终所有标记不全在一条线上)

首先由于\(m\geq 1\),所以\(n=2\)时标记最终一定会走到同一条线上,无解

此时可以证明总存在一种构造方案,使得\(n=3\)时一定有解

对于\(n>3\)的情况,由于给定的\(x<y\),只需要让所有\(y>3\)的平衡器全部设置成\(y\rightarrow x\)即可,最终总能回到\(n=3\)的情况

考虑\(n=3\)时的构造方法,同样,逆序(从右向左)遍历平衡器

假设此时从第\(m\)个平衡器遍历到了第\(k\)个平衡器

\(pos[i]\)来表示只考虑\(k\)\(n\)这些平衡器时,原本在第\(i\)根线上的标记最后位于哪条线上

\(num[i]\)来表示只考虑\(k\)\(n\)这些平衡器时,最后第\(i\)根线上会有几个标记存在

那么我们的遍历也就是每次在最前面多考虑一个平衡器,并且由题意可知,这个平衡器的优先级会更高

考虑此时遍历到的连接\(x\)\(y\)的平衡器\(k\)

如果经过\(k+1\)\(n\)这些平衡器后,\(x\)根线上存在的标记数量比第\(y\)根线上存在的标记数量更多,由于第\(k\)个平衡器优先级更高,那我们就可以从第\(x\)根线上占用一个标记到第\(y\)根线

此时num[x]--,num[y]++,pos[x]=pos[y]

反之,就可以从第\(y\)根线上占用一个标记到第\(x\)根线

考虑到\(n=3\),所以让某个较大的数\(-1\),让某个较小的数\(+1\),可以保证不会有某个数达到\(3\),故肯定存在着方案能够满足题意




程序

#include<bits/stdc++.h>
using namespace std;

int n,m,T;
int x[100050],y[100050];
bitset<50010> bs[50050],tmp;
bool vis[100050];
char ans[100050];

void solve1()
{
    for(int i=1;i<=n;i++)
        bs[i][i]=1;
    for(int i=m;i;i--) //对于每个平衡器连接的两条线,标记其能走到的位置
        bs[x[i]]=bs[y[i]]=bs[x[i]]|bs[y[i]];
    tmp.set(); //全置1
    for(int i=1;i<=n;i++)
        tmp&=bs[i];
    for(int i=1;i<=n;i++) //枚举最终得到的bitset的每一位
    {
        if(tmp[i]) //如果该位值为1
        {
            vis[i]=true; //标记第i条线为最终到达的线
            for(int j=m;j;j--)
            {
                if(vis[y[j]]) //如果y已经被访问到,则x指向y
                {
                    vis[x[j]]=true;
                    ans[j]='v';
                }
                else
                {
                    ans[j]='^'; //否则直接y指向x即可
                    if(vis[x[j]])
                        vis[y[j]]=true;
                }
            }
            ans[m+1]='\0';
            puts(ans+1);
            return;
        }
    }
    puts("-1"); //最终的bitset全为0时
}

void solve2()
{
    if(n==2) //由于m>=1,所以两条线的标记最终一定会走到同一条线上,无解
    {
        puts("-1");
        return;
    }
    int pos[4]={0,1,2,3},num[4]={0,1,1,1};
    for(int i=m;i;i--)
    {
        if(y[i]>3) //对于y>3的线,直接设置y->x即可
        {
            ans[i]='^';
            continue;
        }
        if(num[pos[x[i]]]>num[pos[y[i]]]) //如果x线上的标记数量比较多
        {
            num[pos[x[i]]]--; //从x线上占用一个
            num[pos[y[i]]]++;
            pos[x[i]]=pos[y[i]]; //此时占用过来的标记会到从y线走最终能到的线上
            ans[i]='v';
        }
        else
        {
            num[pos[x[i]]]++;
            num[pos[y[i]]]--;
            pos[y[i]]=pos[x[i]];
            ans[i]='^';
        }
    }
    ans[m+1]='\0';
    puts(ans+1);
}

int main()
{
    scanf("%d%d%d",&n,&m,&T);
    for(int i=1;i<=m;i++)
        scanf("%d%d",&x[i],&y[i]);
    if(T==1)
        solve1();
    else
        solve2();
    return 0;
}

posted @ 2020-10-07 22:48  StelaYuri  阅读(239)  评论(0)    收藏  举报