NOIp模拟赛 现实(DP 拓扑)

题目来源:by lzz

\(Description\)

  给定一张有向图,求对于哪些点,删除它和它的所有连边后,图没有环。
  \(n\leq 5\times10^5,m\leq 10^6\)

\(Solution\)

  题目等价于求所有环的交集。
  首先两个特判:如果原图没有环,输出所有点;如果删掉原图的某个环后,仍存在环,输出\(0\)。这也是不少分了。
  先求出图中的某个环,环交当然在这个环上。我们只需要处理这个环。
  把环拆成链,发现所有除它外的环只有两种情况:
1143196-20180911110852460-2023905187.png
  对于第一种情况,
1143196-20180911112710466-1008512141.png
1143196-20180911111733133-398286514.png
  如果做过这个链的Subtask,很容易发现(倒也显然)合法的点只可能是这些环的交集(把红边看成线段,就是求区间的交)。之前的第二次判环可以拓扑,然后利用拓扑序从出度为0的点更新能到它的点的最左位置\(pl\)、从入度为0的点更新它到的点的最右位置\(pr\)。然后就可以找到最右的左端点和最靠左的右端点。

  对于第二种情况,
1143196-20180911112350014-1321444475.png
  显然,如果存在红边\(x\rightarrow y\),则\(x,y\)之间的点都不是合法的。
  依旧利用拓扑序从出度为0的点更新到它的点的最右位置\(pr\),然后扫一遍。
  两种情况都合法的点就是答案了。

  发现图中只有两个环,且环交为1个点时,这个点是合法的,但是删掉环边后图仍存在环,会返回无解。我们发现如果将每个点拆成入点和出点,这种情况就可以处理了。即把环交从点集变成边集。
  复杂度\(O(n+m)\)

#include <cstdio>
#include <cctype>
#include <vector>
#include <cstring>
#include <algorithm>
//#define gc() getchar()
#define MAXIN 300000
#define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
const int N=1e6+5,M=1e6+5+N;

int n,m,Enum,H[N],nxt[M],to[M],dgr[N],cir[N],sz,pre[N],fa[N],q[N],pl[N],pr[N];
bool find_circle,vis[N],ins[N],isc[M],ok[N];
std::vector<int> ans;
char IN[MAXIN],*SS=IN,*TT=IN;

inline int read()
{
    int now=0;register char c=gc();
    for(;!isdigit(c);c=gc());
    for(;isdigit(c);now=now*10+c-'0',c=gc());
    return now;
}
inline void AE(int v,int u)
{
    to[++Enum]=v, nxt[Enum]=H[u], H[u]=Enum;
}
void DFS(int x)
{
    vis[x]=ins[x]=1;
    for(int i=H[x],v; i; i=nxt[i])
        if(!vis[v=to[i]])
        {
            fa[v]=x, pre[v]=i, DFS(v);
            if(find_circle) return;
        }
        else if(ins[v])
        {
            for(int p=x; p!=v; p=fa[p]) cir[++sz]=p, isc[pre[p]]=1;
            cir[++sz]=v, isc[i]=1;
            std::reverse(cir+1,cir+1+sz);
            find_circle=1; return;
        }
    ins[x]=0;
}
bool Toposort()
{
    int h=0,t=0;
    for(int i=1; i<=Enum; ++i) if(!isc[i]) ++dgr[to[i]];//度也是删环后的!
    for(int i=1; i<=n; ++i) if(!dgr[i]) q[t++]=i;
    while(h<t)
    {
        int x=q[h++];
        for(int i=H[x]; i; i=nxt[i])
            if(!isc[i] && !--dgr[to[i]]) q[t++]=to[i];
    }
    return t==n;
}
void Solve()
{
    for(int i=1; i<=n; ++i)
        if(!vis[i]) {DFS(i); if(find_circle) break;}
    if(!find_circle)
    {
        n>>=1;
        for(int i=1; i<=n; ++i) ans.push_back(i);
        return;
    }
    if(!Toposort()) return;

    int ansl=1,ansr=sz;
    for(int i=1; i<=sz; ++i) pl[cir[i]]=pr[cir[i]]=i;
    for(int i=n,x; i; --i)
    {
        if(!pl[x=q[i]]) pl[x]=N;//避免环外的影响 
        for(int j=H[x]; j; j=nxt[j])
            if(!isc[j]/*!*/) pl[x]=std::min(pl[x],pl[to[j]]);//非环边!又忘判了 
    }
    for(int i=1; i<=sz; ++i)
        if(pl[cir[i]]<i) {ansr=i; break;}//对于左端端点应该有pl[i]==i 
    for(int i=1,x; i<=n; ++i)
    {
//      if(!pr[x=q[i]]) pr[x]=0;
        for(int j=H[x=q[i]]; j; j=nxt[j])
            if(!isc[j]) pr[to[j]]=std::max(pr[to[j]],pr[x]);
    }
    for(int i=sz; i; --i)
        if(pr[cir[i]]>i) {ansl=i; break;}
    if(ansl>ansr) return;

    memset(pr,0,sizeof pr);
    for(int i=1; i<=sz; ++i) pr[cir[i]]=i;
    for(int i=n,x; i; --i)//对另一个方向的pr再求一次 
    {
//      if(!pr[x=q[i]]) pr[x]=0;
        for(int j=H[x=q[i]]; j; j=nxt[j])
            if(!isc[j]) pr[x]=std::max(pr[x],pr[to[j]]);
    }
    int nowr=0;
    for(int i=1; i<=sz; ++i)
    {
        if(i>=nowr) ok[i]=1;
        nowr=std::max(nowr,pr[cir[i]]);
    }
    for(int i=ansl; i<ansr; i+=2) if(ok[i]) ans.push_back(cir[i]);//ansl一定是个入点 
    std::sort(ans.begin(),ans.end());
}

int main()
{
    n=read();
    for(int i=1; i<=n; ++i) AE(i+n,i);//in:x out:x+n 参数顺序!
    for(int m=read(); m--; AE(read(),read()+n));
    n<<=1, Solve();
    printf("%d\n",ans.size());
    for(int i=0,l=ans.size(); i<l; ++i) printf("%d ",ans[i]);

    return 0;
}

转载于:https://www.cnblogs.com/SovietPower/p/9626394.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
NOI(全国青少年信息学奥林匹克竞模拟的测试数据是指用于评测参选手的程序的输入和对应的输出。测试数据是非常重要的,因为它决定了参选手的程序能否正确地解决问题。 在NOI模拟中,测试数据具有以下特点: 1.充分覆盖:测试数据应涵盖各种可能的输入情况,包括边界条件和极端情况。通过提供不同的测试数据,可以考察选手对问题的全面理解和解决能力。 2.随机性和均衡性:为了公平起见,测试数据应该是随机生成的,而不是针对某个特定算法或解法设计的。同时,测试数据应该是均衡的,即各种情况的概率应该大致相等,以避免偏向某些解法。 3.合理性和可行性:测试数据应该是合理和可行的,即符合题目要求的输入数据,并且是选手能够通过编写程序来处理的。测试数据应该考虑到程序的限制和时间复杂度,以充分测试选手的编程能力。 NOI模拟的测试数据通常由经验丰富的考题组负责生成。他们会根据题目的要求和限制,设计出一组合理、充分、随机和均衡的测试数据,以确保参选手的程序在各种情况下都能正确运行,并且能通过性能测试。 总之,测试数据在NOI模拟中起到了至关重要的作用,它既考察了选手对问题的理解和解决能力,又提高了选手编程的技巧和效率。同时,合理和恰当的测试数据也是公平竞的保证,确保每个参选手有相同的机会和条件进行竞争。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值