Jzoj3486 道路改建

227 篇文章 3 订阅
134 篇文章 0 订阅

人称不死将军的林登·万,与他的兄弟林登·图两人的足迹踏遍了地球的每一寸土地。他们曾将战火燃遍了世界。即使是lifei888这样的强悍人物也从来没有将他彻底击败。
这一次,林登·万在N个城市做好了暴动的策划。然而,在起事的前一天,将军得知计划已经泄漏,决定更改计划,集中力量掌握一部分城市。
具体来说,有M条单向边连接着这N座城市。对于两座城市A,B,如果它们能够通过单向边直接或间接的互相到达,那么就林登·万可以同时控制A,B两座城市而不至于分散力量,反之则会被lifei888各个击破。
为了扩大成果,将军还组织了人手改建道路。这些人可以在起事前将其中一条有向边改变成双向边(注意只能改建其中一条单向边,另外M-1条单向边保持不变),现在,将军想要知道他通过改建其中一条单向边最多能控制几座城市,以及被改建的这一条单向边有多少种选择方案。

这个题好恶心啊,题面让题解找来找去都是些不相干的东西。。。

正确做法:tarjan缩点+bitset求交集

我们将原图缩成一个dag,计f[i]表示节点i能到达的节点集合,g[i]表示能到达i的节点集合(可以dp求出)

那么答案就是Max{f[u]∩g[v],(u,v)∈E}

但是写了n久也没过,发现一系列的坑:

1.题面描述不清楚,这里要求的是最大的联通块大小而不是所有在一个大于一的联通块里的点

2.因为要输出所有的方案,所以对于一些改了之后毫无意义的边也要输出(比如一个联通块大小为1000,另一个为10,则后者里面所有的边也要输出,虽然它对答案毫无帮助)

于是参考了一下别人的题解,这才注意到原来n<=2000

所以可以直接用Floyd做传递闭包啊!用bitset不就好了吗,O(n^2)又好写又快(因为|E|是n^2级别的,tarjan常数较大)

所以就很愉快地切掉了,code连1kb都不到

#pragma GCC opitmize("O3")
#pragma G++ opitmize("O3")
#include<bitset>
#include<vector>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 2010
using namespace std;
vector<int> A;
bitset<N> f[N],g[N];
int n,m,u[1000010],v[1000010],ans=0;
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) f[i][i]=g[i][i]=1;
	for(int i=1;i<=m;++i){
		scanf("%d%d",u+i,v+i);
		f[u[i]][v[i]]=1;
	}
	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j)
			if(f[j][i]) f[j]|=f[i];
	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j)
			g[j][i]=f[i][j];
	int Mx=0;
	for(int i=1;i<=n;++i) Mx=max(Mx,(int)(f[i]&g[i]).count());
	for(int c,i=1;i<=m;++i)
		if((c=max(Mx,(int)(f[u[i]]&g[v[i]]).count()))>ans){
			ans=c; A.clear(); A.push_back(i);
		} else if(c==ans) A.push_back(i);
	printf("%d\n%d\n",ans,A.size());
	for(int i=0;i<A.size();++i) printf("%d ",A[i]);
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值