算法竞赛——进阶指南——acwing 368. 银河 SCC+差分约束+spfa 一道不错的综合题

这是一道结合了强连通分量(SCC)和差分约束系统的综合题目。由于边权仅限0/1,通过建立超级源点并设置初始值,可以确保寻找每个变量的最小值。通过最长路算法,可以判断在存在正环的情况下是否存在解。在建图阶段,可以同时检查是否有边权为1的环,以此确定无解情况。最后使用SPFA算法求解最长路。
摘要由CSDN通过智能技术生成

从题面上看,很容易想到是差分约束。

但是发现数据范围比较大,无法判断无解情况(复杂度为O(nm)).

但我们仔细读题,发现:边权只有0/1。

由于是求每个未知量得最小值。

我们建一个超级源点S,d[S]=0,d[x] - d[S] = 1;这样就能保证每颗星星得最小值为1.

然后把条件按最长路建图。(为什么是最长路,可以仔细想一下,我之前差分约束总结里有详细说)

由于是最长路,无解的情况是存在正环,而由于只有0/1,所以某个环上只要有一个1,那么就无解。

也就是说,我们可以在建图时先判断是否有解,再用spfa跑最长路即可。

判环上是否有边权为1,可以先求出图的SCC,在每个SCC上遍历所有边,只要存在边权为1,则无解。

 

 

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define ls (o<<1)
#define rs (o<<1|1)
#define pb push_back
const double PI= acos(-1.0);
const int M = 2e5+7;
int head[M],cnt=1,hc[M],tc=1;
void init(){cnt=1,memset(head,0,sizeof(head));}
struct EDGE{int to,nxt,w;}ee[M*2],ec[M];
void add(int x,int y,int w){ee[++cnt].nxt=head[x],ee[cnt].w=w,ee[cnt].to=y,head[x]=cnt;}
void add_c(int x,int y,int w)
{
	ec[++tc].nxt=hc[x];
	ec[tc].to=y;
	ec[tc].w=w;
	hc[x]=tc;
}
int dfn[M],low[M];
int sk[M],top,ct,sz,in[M],c[M];
vector<int>scc[M];
int vs[M],d[M];
void tarjan(int x)
{
	low[x]=dfn[x]=++sz;
	sk[++top]=x,in[x]=1;
	for(int i=head[x];i;i=ee[i].nxt)
	{
		int y=ee[i].to;
		if(!dfn[y])
		{
			tarjan(y);
			low[x]=min(low[x],low[y]);
		}
		else if(in[y])low[x]=min(low[x],dfn[y]);
	}
	if(dfn[x]==low[x])
	{
		ct++;int y;
		do{
			y=sk[top--],in[y]=0;
			c[y]=ct,scc[ct].pb(y);
		}while(x!=y);
	}
}
void spfa()
{
	queue<int>q;q.push(0);vs[0]=1;
	while(q.size())
	{
		int x=q.front();q.pop();
		vs[x]=0;
	//	cout<<x<<"  -   "<<endl;
		for(int i=hc[x];i;i=ec[i].nxt)
		{
			int y=ec[i].to,w=ec[i].w;
		//	cout<<x<<" "<<y<<"  "<<w<<"  "<<d[y]<<" "<<d[x]<<endl;
			if(d[y]<d[x]+w)
			{
				d[y]=d[x]+w;
				if(!vs[y])q.push(y),vs[y]=1;
			}
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
  	cin.tie(0);
  	int n,m;
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int x,y,t;
		cin>>t>>x>>y;
		//d[v]-d[u]>=w  /  u -> v : w 
		if(t==1)add(x,y,0),add(y,x,0);
		if(t==2)add(x,y,1);//d[x]<d[y]  / d[y]-d[x]>=1 
		if(t==3)add(y,x,0);//d[x]>=d[y] / d[x]-d[y]>=0
		if(t==4)add(y,x,1);//d[x]>dy] / d[x]-d[y]>=1	
		if(t==5)add(x,y,0);//d[x]<=d[y]
	} 
	//不能存在  环上边权为1的情况。
	for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i);
 	bool f=true;
 	for(int i=1;i<=ct;i++)
 		for(auto x:scc[i])
 			for(int j=head[x];j;j=ee[j].nxt)
			{
				if(c[ee[j].to]!=i)continue;
			//	cout<<x<<" "<<ee[j].to<<"  "<<c[x]<<"  "<<c[ee[j].to]<<"  "<<ee[j].w<<endl;
				if(ee[j].w==1)f=false;
			}
	if(!f){
		cout<<-1<<endl;
		return 0;
	}
	for(int x=1;x<=n;x++)
	{
		for(int i=head[x];i;i=ee[i].nxt)
		{
			int y=ee[i].to,w=ee[i].w;
			if(c[x]==c[y])continue;
			add_c(c[x],c[y],w);
		}
	}
	for(int i=1;i<=ct;i++)add_c(0,i,1);//超级源点,保证了每颗星星的亮度大于等于1 
	spfa();
	ll ans=0;
	for(int i=1;i<=n;i++)ans+=d[i]*scc[i].size();
	cout<<ans<<endl;
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值