P3119 [USACO15JAN]Grass Cownoisseur G

3 篇文章 0 订阅
1 篇文章 0 订阅
题目:

给定一个 n n n个结点, m m m条边的有向图,现在从结点1出发,最后回到结点1,每个结点有权值1,访问结点时可以获得该权值,可以经过一个结点多次,但只能获得一次权值,在这个过程中有最多1次机会可以逆向走边,问最后可以得到的最大权值和是多少。
( 1 ≤ n , m ≤ 100000 ) (1 \le n,m \le 100000) (1n,m100000)

题解:

看到这种可以经过一个结点多次,但一个结点只算一次贡献,就可以想到需要 t a r j a n tarjan tarjan求强连通分量,然后缩点。缩完点以后图变成了一个 D A G DAG DAG(下面所说的原图和结点均指缩点后的图),这种有特殊条件的最长路需要用分层图的思想。将状态(结点编号 n o d e node node,访问至该结点有无使用过逆向机会 f l a g flag flag)看成新图的结点。然后建图:假设原图 u → v u \rightarrow v uv,则 ( u , 0 ) → ( v , 0 ) , ( u , 1 ) − > ( v , 1 ) , ( v , 0 ) → ( u , 1 ) (u,0) \rightarrow (v,0),(u,1)->(v,1),(v,0) \rightarrow (u,1) (u,0)(v,0),(u,1)>(v,1),(v,0)(u,1),跑 s p f a spfa spfa求最长路即可(变负号跑 d i j k s t r a dijkstra dijkstra也行,因为这样建图是没有环的),最后的答案就是 d i s s c c 1 , 1 dis_{scc_1,1} disscc1,1 s c c 1 scc_1 scc1为结点1所在的强联通分量编号。
那么这种做法是怎么保证同一个点只算一次贡献的呢?由于缩点完的图是一个 D A G DAG DAG,所以在同一层相同的点不会在一条路径中出现多次,不然就有环了,那么从0层跳到1层以后怎么保证0层已经在路径中的点不会在1层中再经过一次呢?0层中已经在路径中的点是不可能到达结点1的,因为在0层是从结点1出发的,如果又可以回到结点1,就和 D A G DAG DAG矛盾了;而在1层中要到达结点1,所以如果在到达结点1的路径中又经过了0层中的点,按照之前的结论,就不可能到达结点1了。所以可以保证一个结点在从结点1到结点1的过程中最多只会经过1次。
基于以上的分析,这种做法对于从结点1到其他点的答案是不对的。

复杂度: O ( 能 过 ) O(能过) O()
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<string>
#include<bitset>
#include<sstream>
#include<ctime>
//#include<chrono>
//#include<random>
//#include<unordered_map>
using namespace std;

#define ll long long
#define ls o<<1
#define rs o<<1|1
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define sz(x) (int)(x).size()
#define all(x) (x).begin(),(x).end()
const double pi=acos(-1.0);
const double eps=1e-6;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
const int maxn=1e5+5;
ll read(){
	ll x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int n,m,num,tp,sccnum;
vector<int>g[maxn],g2[maxn],r[maxn];
int dfn[maxn],low[maxn],stk[maxn],dis[maxn][2],inq[maxn][2],we[maxn],scc[maxn];
struct node{
	int no,f,d;
};
queue<node>q;
void tarjan(int u){
	dfn[u]=low[u]=++num;
	stk[++tp]=u;
	for(auto v:g[u]){
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(!scc[v]){
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(low[u]==dfn[u]){
		++sccnum;
		while(stk[tp]!=u){
			scc[stk[tp]]=sccnum;
			we[sccnum]++;
			--tp;
		}
		scc[stk[tp]]=sccnum;
		we[sccnum]++;
		--tp;
	}
}
void rebuild(){
	for(int u=1;u<=n;u++){
		for(auto v:g[u]){
			int nu=scc[u],nv=scc[v];
			if(nu!=nv){
				g2[nu].pb(nv);
				r[nv].pb(nu);
			}
		}
	}
}
void spfa(){
	for(int i=1;i<=n;i++){
		for(int j=0;j<2;j++){
			dis[i][j]=-INF;
		}
	}
	dis[scc[1]][0]=0;
	q.push(node{scc[1],0,dis[scc[1]][0]});
	inq[scc[1]][0]=1;
	while(!q.empty()){
		node u=q.front();
		q.pop();
		inq[u.no][u.f]=0;
		for(auto v:g2[u.no]){
			if(dis[u.no][u.f]+we[v]>dis[v][u.f]){
				dis[v][u.f]=dis[u.no][u.f]+we[v];
				if(!inq[v][u.f]){
					q.push(node{v,u.f,dis[v][u.f]});
					inq[v][u.f]=1;
				}
			}
		}
		if(u.f==0){
			for(auto v:r[u.no]){
				if(dis[u.no][u.f]+we[v]>dis[v][1]){
					dis[v][1]=dis[u.no][u.f]+we[v];
					if(!inq[v][1]){
						q.push(node{v,1,dis[v][1]});
						inq[v][1]=1;
					}
				}
			}
		}
	}
}
int main(void){
	// freopen("in.txt","r",stdin);
	scanf("%d%d",&n,&m);
	int u,v;
	for(int i=1;i<=m;i++){
		scanf("%d%d",&u,&v);
		g[u].pb(v);
	}
	for(int i=1;i<=n;i++){
		if(!dfn[i]){
			tarjan(i);
		}
	}
	rebuild();
	spfa();
	printf("%d\n",dis[scc[1]][1]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值