CF1062F Upgrading Cities

Link
一个思路是计算出每个点可以到达的点集和可以到达该点的点集的并的大小(S_u),但是很显然这样做的复杂度不会低于(O(frac{n^2}{omega}))
但是注意到我们并不需要精确地计算出所有点的(S),对于一定不可能在答案中的点(u),我们可以不计算该点的(S)
考虑利用拓扑排序计算该点可以到达的点数,计算完之后把所有边反向再计算可以到达该点的点数,途中维护尚未入队的点数(res)
不难发现任意时刻队列中的点之间都没有连边。
如果当前队列中有超过两个点,那么这些点一定都是不可能被计入答案的,因此我们可以直接无视。
如果当前队列中仅有一个点(u),那么这个点可以到达剩下尚未入队的点,因此我们让(S_u)加上(res)
如果当前队列中有两个点(u,v),不妨考虑只处理(u),如果(v)存在某个出点(w)入度为(1),那么说明(u)点到达不了(w),再加上(u)无法到达(v)(u)就可以被无视了。否则(v)的所有出点入度都大于(1),那么(u)一定可以到达(v)的所有出点,因此我们让(S_u)加上(res)
这样做的话时间复杂度是(O(n+m))的。

#include<queue>
#include<cctype>
#include<cstdio>
#include<vector>
#include<algorithm>
const int N=300007;
int n,m,u[N],v[N],deg[N],cnt[N];std::vector<int>e[N];std::queue<int>q;
int read(){int x=0,c=getchar();while(isspace(c))c=getchar();while(isdigit(c))(x*=10)+=c&15,c=getchar();return x;}
int check(int u){for(int v:e[u])if(deg[v]==1)return 0;return 1;}
void work()
{
    int res=n;
    for(int i=1;i<=n;++i) e[i].clear();
    for(int i=1;i<=m;++i) e[u[i]].push_back(v[i]),++deg[v[i]];
    for(int i=1;i<=n;++i) if(!deg[i]) q.push(i),--res;
    for(int u;!q.empty();)
    {
	u=q.front(),q.pop();
	if(q.empty()) cnt[u]+=res; else if(q.size()==1) cnt[u]+=check(q.front())*res;
	for(int v:e[u]) if(!--deg[v]) q.push(v),--res;
    }
}
int main()
{
    n=read(),m=read();int ans=0;
    for(int i=1;i<=m;++i) u[i]=read(),v[i]=read();
    work();
    for(int i=1;i<=m;++i) std::swap(u[i],v[i]);
    work();
    for(int i=1;i<=n;++i) ans+=cnt[i]>=n-2;
    printf("%d",ans);
}
原文地址:https://www.cnblogs.com/cjoierShiina-Mashiro/p/12669395.html