P6134 [JSOI2015]最小表示

Link\text{Link}

题意

给定一个有向无环图,节点编号为 1N1\sim N,有 MM 条边,你可以删除其中的某些边,使其中任意两点的连通性保持不变(若原图中点 iijj 有一条路径,那么新图中 iijj 也有一条路径,反之亦然),求最多能删除几条边?

分析

先分析一下样例:

由样例解释得,边 131\Rightarrow 3151\Rightarrow 5 是可以删去的,因为 131\Rightarrow 3 可以用 1231\Rightarrow 2\Rightarrow 3 代替;151\Rightarrow 5 可以用 1351\Rightarrow 3\Rightarrow 5 代替,进而可以代替为 12351\Rightarrow 2\Rightarrow 3\Rightarrow 5

所以,一条边 iji\Rightarrow j 可以被删去,仅当存在另一条路径可以替代它,题目中又保证任意两点之间至多只有一条边,所以另一条路径的长度必然大于等于 22,即存在一条路径 iji\Rightarrow …\Rightarrow j,于是问题转化为求 i=1nj=1n[ij][ij]\sum_{i=1}^n\sum_{j=1}^n[i\Rightarrow j]\land[i\Rightarrow …\Rightarrow j]

设边集为 EE,式子可进一步转化为 eE[e.ue.v]\sum_{e\in E}[e.u\Rightarrow …\Rightarrow e.v]

wi,jw_{i,j} 表示存在一条边 jij\Rightarrow idi,jd_{i,j} 表示存在一条路径 jij\Rightarrow …\Rightarrow i,即这条路径的边数大于等于 22,则答案为 eEde.v,e.u\sum_{e\in E}d_{e.v,e.u}

我们对图进行拓补排序,遍历每条边 xyx\Rightarrow y,我们要用 dx,kd_{x,k}wx,kw_{x,k} 更新 dy,kd_{y,k},若 kkxx 存在边数大于等于 11 的路径,那么经过 xyx\Rightarrow y 后,就存在边数大于等于 22 的路径了,即 dy,k=dy,kdx,kwx,kd_{y,k}=d_{y,k}\lor d_{x,k}\lor w_{x,k}

这样做的时间复杂度是 O(nm)O(nm)nmnm 最大可能为 3×104×105=3×1093\times 10^4\times 10^5=3\times10^9,不能通过此题,不过 dy,k=dy,kdx,kwx,kd_{y,k}=d_{y,k}\lor d_{x,k}\lor w_{x,k} 部分可以使用 bitset\text{bitset} 优化,复杂度为 O(nm32)O(\frac{nm}{32}),可以通过此题。

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
long long read(){
	long long x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
	while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
void write(long long x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
const int N=3e4+10;
int n,m,in[N],ans;
vector<int>e[N];
bitset<N>d[N],w[N];
void topu(){
	queue<int>q;
	for(int i=1;i<=n;i++)
		if(!in[i])
			q.push(i);
	while(q.size()){
		int x=q.front();q.pop();
		for(int i=0,y;i<e[x].size();i++){
			in[y=e[x][i]]--;
			d[y]|=d[x]|w[x];
			if(!in[y])
				q.push(y);
		}
	}
}
int main(){
	n=read();m=read();
	for(int i=1,x,y;i<=m;i++){
		x=read();y=read();
		e[x].push_back(y);
		in[y]++;
		w[y][x]=1;
	}
	topu();
	for(int i=1;i<=n;i++)
		for(int j=0;j<e[i].size();j++)
			ans+=d[e[i][j]][i];
	write(ans);
	return 0;
}
posted @ 2021-11-07 11:14  luckydrawbox  阅读(11)  评论(0)    收藏  举报  来源