NOI 模拟试题(二)

                        逃离计划

【题目背景】
逃亡主义,全民公决。
【题目描述】
有 n 名议员,m 个逃离方案。每一个逃离方案都有可能通过或者不通过。可以有多个
方案同时通过。对于 m 个逃离方案的通过与否序列,称其为总方案 S。(即 m 个元素的
01 序列 0 表示不通过,1 表示通过)
第 i 名议员手中有 a[i]张选票,他会对 a[i]个方案进行投票,态度为通过或者不通过。
你需要根据所有的选票计算出总方案。并且要求每一个议员的选票中有超过一半的选票与
最后的总方案的态度相符。你同时还应当计算出哪一些方案的通过与否是确定的(即这个
方案在所有可能的总方案中的态度是一样的)。
【输入】(vote.in)
第一行有两个用一个空格分开的数你 n,m,意义同上。
接下来的 n*3 行,每 3 行描述了一个人的投票方案
1. a[i]意义同上
2. a[i]个正整数 表示这个人投票的方案
3. a[i]个整数只取 0 或 1 表示对上一行描述的 a[i]个方案的态度。0 表示不通过,
1 表示通过。态度与方案从左到右一一对应。
【输出】(vote.out)
如果不存在一个满足条件的总方案,输出’NO’(单引号不输出)否则
第一行为一个整数 S 表示总方案中有 S 个逃离方案的态度是确定的。接下来的 S 行,
每一行表示一个确定的逃离方案的标号(升序)。
【样例输入】
2 4
4
1 2 3 4
0 0 0 0
4
1 2 3 4
1 0 0 0
【样例输出】
3
2
3
4
【数据规模】
30%的数据 n,m<=200;
50%的数据 n,m<=2500
100%的数据 n<=10000 m<=20000 1<=a[i]<=4
【样例解释】
两种可行的方案:
0 0 0 0
1 0 0 0

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <bitset>
#include <vector>
using namespace std;
const int N=100010,M=200010;
struct Edge{
    int cnt,fir[M*2],from[M*8];
    int nxt[M*8],to[M*8];
    void addedge(int a,int b){
        nxt[++cnt]=fir[a];
        to[fir[a]=cnt]=b;
        from[cnt]=a;
    }
}e,g;

int tim,vis[M*2],low[M*2];
int scnt,st[M*2],scc[M*2],top;
int n,m,a[N],b[N],id[N][2],idx;
void Tarjan(int x){
    vis[x]=low[x]=++tim;st[++top]=x;
    for(int i=e.fir[x];i;i=e.nxt[i])
        if(!vis[e.to[i]]){
            Tarjan(e.to[i]);
            low[x]=min(low[x],low[e.to[i]]);
        }
        else if(!scc[e.to[i]])
            low[x]=min(low[x],vis[e.to[i]]);
    if(low[x]==vis[x]){
        ++scnt;
        while(true){
            int y=st[top--];
            scc[y]=scnt;
            if(x==y)break;
        }
    }       
}

bool Check(){
    for(int i=1;i<=idx;i++)
        if(!vis[i])Tarjan(i);
    for(int i=1;i<=m;i++)
        if(scc[id[i][0]]==scc[id[i][1]])
            return false;
    return true;        
}

int ans[M*2],calc;
int in[M*2],rk[M*2],ord[M*2];

int main(){
    freopen("vote.in","r",stdin);
    freopen("vote.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        id[i][0]=++idx;
        id[i][1]=++idx;
    }
    for(int i=1,k;i<=n;i++){
        scanf("%d",&k);
        for(int j=1;j<=k;j++)
            scanf("%d",&a[j]);
        for(int j=1;j<=k;j++)
            scanf("%d",&b[j]);
        if(k<=2){
            for(int j=1;j<=k;j++)
                e.addedge(id[a[j]][b[j]^1],id[a[j]][b[j]]);
        }   
        else{
            for(int t1=1;t1<=k;t1++)
                for(int t2=1;t2<=k;t2++)
                    if(t1!=t2)e.addedge(id[a[t1]][b[t1]^1],id[a[t2]][b[t2]]);
        }
    }
    if(!Check()){
        puts("NO");
        return 0;
    }


    for(int i=1;i<=e.cnt;i++){
        int x=scc[e.from[i]],y=scc[e.to[i]];
        if(x!=y){g.addedge(x,y);in[y]+=1;}
    }
    top=tim=0;
    for(int i=1;i<=scnt;i++)
        if(!in[i])st[++top]=i;
    while(top){
        int x=st[top--];rk[x]=++tim;ord[tim]=x;
        for(int i=g.fir[x];i;i=g.nxt[i])
            if(--in[g.to[i]]==0)
                st[++top]=g.to[i];
    }   
    for(int t=1;t<=m;t++){
        int S=scc[id[t][0]],T=scc[id[t][1]];
        if(rk[S]>rk[T])swap(S,T);
        st[top=1]=S;int flag=0;
        for(int i=rk[S];i<=rk[T];i++)
            vis[ord[i]]=0;
        while(top){
            int x=st[top--];vis[x]=true;
            for(int i=g.fir[x];i;i=g.nxt[i])
                if(rk[g.to[i]]<=rk[T]){
                    if(g.to[i]==T){flag=1;break;}
                    if(!vis[g.to[i]])st[++top]=g.to[i];
                }
        }
        if(flag)ans[++calc]=t;
    }
    printf("%d\n",calc);
    for(int i=1;i<=calc;i++)
        printf("%d\n",ans[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值