[CSP-S模拟测试]:世界线(DFS+bitset)
题目描述
时间并不是一条单一的线,而是有许多世界线构成的流。
在一些时刻,世界线会发生分裂;同样的,它们也有可能在一些时刻收束在一起。如果将这些时刻抽象成点,那么这些世界线构成的网络,实际上是一张有向无环图。
$Okabe$想要改变世界线的构造,他认为世线是优美的,当且仅当其中不存在三个点$u,v,t$,其中$u$到$v$有连边,$v$到$t$有连边,而$u$到$t$没有连边。
作为世界的观测者,$Okabe$已经知道了世界线的构成。现在他想知道,在不删边的情况下,至少要连接多少条边,才能得到优美的世界线。
输入格式
第一行两个整数$n,m$,表示点数和边数。
接下来$m$行,每行两个整数$n,m$,表示点数和边数。
接下来$m$行,每行两个整数$u,v$,表示$u$到$v$有一条有向边。
输出格式
仅包含一个整数,表示答案。
样例
样例输入:
5 5
1 2
1 3
2 3
3 4
4 5
样例输出:
5
数据范围与提示
样例解释:
还需要连边$(1,4),(1,5),(2,4),(2,5),(3,5)$。
数据范围:
保证$1\leqslant u,v\leqslant n$,给出的图是一张有向无环图,且没有重边和自环。
各个测试点还满足如下约束:
题解
先来明确题意,一个点如果是另一个点的父亲,那么也一定是那个点的爷爷,如下图:
那么我们需要连边的数量就是每个点的祖先数$-1$,为什么呢?再来看一张图:
$\rightarrow$$\rightarrow$$\rightarrow$
通过上面这四张图,我们发现,每个点都需要向除了它的直接父亲的所有点有连边。
那么对于$n=m-1$就已经轻松解决了。
但是你会发现样例还是过不了,我们先来看一下样例的图:
我们发现,对于点$3$,从$1$有两条路径可以到达$1$,那么我们考虑如何去重。
最朴素的做法就是记录一下所有祖先,但是这步操作可以$bitset$优化。
没错,这就是正解。
需要注意的是空间问题,分两次操作,第一次考虑前$30000$个点,第二次考虑后$30000$个点即可。
时间复杂度:$\Theta(\frac{n(n+m)}{w})$。
期望得分:$100$分。
实际得分:$100$分。
代码时刻
#include<bits/stdc++.h>
using namespace std;
struct rec{int nxt,to;}e[100001];
int head[60001],cnt;
int n,m;
int du[60001],rd[60001];
int lft,rht;
int now,won,dp[2][60001],pos;
bitset<30001> bit[60001];
int ans,sum;
void add(int x,int y)
{
e[++cnt].nxt=head[x];
e[cnt].to=y;
head[x]=cnt;
}
void work(int x)
{
for(int i=head[x];i;i=e[i].nxt)
{
bit[e[i].to]|=bit[x];
du[e[i].to]--;
if(!du[e[i].to])
{
dp[!pos][++won]=e[i].to;
sum--;
ans+=bit[e[i].to].count();
}
}
}
void judge()
{
now=0,sum=n;
for(int i=1;i<=n;i++)du[i]=rd[i];
for(int i=1;i<=n;i++)
if(!du[i])
{
dp[0][++now]=i;
sum--;
if(lft<=i&&i<=rht)ans++;
}
pos=0;
while(sum)
{
won=0;
for(int i=1;i<=now;i++)work(dp[pos][i]);
pos^=1;
now=won;
}
}
void check()
{
for(int i=1;i<=n;i++)bit[i].reset();
for(int i=lft;i<=min(n,rht);i++)bit[i].set(i-lft);
judge();
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
du[y]++;
}
for(int i=1;i<=n;i++)rd[i]=du[i];
lft=1;
rht=30000;
while(lft<=n)
{
check();
lft+=30000;
rht+=30000;
}
printf("%d",ans-n-m);
return 0;
}
rp++