[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++

posted @ 2019-09-17 17:05  HEOI-动动  阅读(248)  评论(0编辑  收藏  举报