【BZOJ1202】差分约束系统 or 并查集

题意很简单

真是一道绝世好题啊!

一句话题意
给一段区间的和,问有没有冲突。
(是不是真的一句话(花鸡))
分析
这个题不久随便搞搞就行了嘛,我判断一下有没有冲突就行了啊,这很难吗?

五分钟后

这咋写啊?

qwq在线丢人

???

算了,我不会这个题。

等等!
s到t是v,那不就是sum[t]-sum[s-1]=v吗?那不就是sum[t]-sum[s-1]<=v , sum[s-1]-sum[v]<=-v吗?
这不就是差分约束吗?
那不就做完了嘛!

建完了图,跑spfa,发现有的点进队列>=n次,那就说明有环,那就说明t-s=v,s-t=v同时存在,那就说明这个是有冲突的了。

但是我们不知道起点是啥,我们就把没跑过的点都作为起点来一遍吧qwq。

#include <bits/stdc++.h>
#define sc(n) scanf("%d",&n)
#define pt(n) printf("%d\n",n)
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define vi vector<int>
#define vl vector<long long>
#define pb push_back
#define INF 0X3f3f3f3f
using namespace std;
const int maxn = 200;
int n,m;
int inq[maxn];
int vis[maxn];
int dis[maxn];
struct node
{
	int to;
	int w;
};
vector<node> G[maxn];
queue<int> q;
bool spfa(int s)
{
	fill(dis,dis+maxn,-INF);
	q.push(s);
	dis[s] = 0;
	inq[s] = 1;
	vis[s]++;
	while(!q.empty())
	{
		int now = q.front();
		q.pop();
		vis[now]++;
		inq[now] = 0;
		if(vis[now]>=n) return false;
		for(int i=0;i<G[now].size();i++)
		{
			int v = G[now][i].to;
			if(dis[v]<dis[now]+G[now][i].w)
			{
				dis[v] = dis[now]+G[now][i].w;
				if(!inq[v])
				{
					inq[v] = 1;
					q.push(v);
				}
			}
		}
	}
	return true;
}
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		for(int i=0;i<maxn;i++) G[i].clear();
		while(!q.empty()) q.pop();
		bool found = true;
		memset(vis,0,sizeof(vis));
		memset(inq,0,sizeof(inq));
		scanf("%d%d",&n,&m);
		for(int i=0;i<m;i++)
		{
			int s,t,w;
			scanf("%d%d%d",&s,&t,&w);
			G[s-1].pb({t,w});
			G[t].pb({s-1,-w});
		}
		for(int i=0;i<=n;i++)
		{
			if(!vis[i])
			{
				if(!spfa(i))
				{
					found = false;
				}
			}
		}
		if(found) printf("true\n");
		else printf("false\n");
	}
	return 0;
}

先别急着走鸭。

下面我们来说一说并查集的做法。
并查集的做法理解起来就有一点难了。

首先我们明确一个事情,如果两段区间有公共点,我们是不是就可以算出这两段和合并后的这一段的信息了。
举个栗子:
sum[b]-sum[a]=k1
sum[c]-sum[a]=k2
那么sum[b]-sum[c] = k1-k2

那么这个东西有什么用处呢?

如果我们知道了s和t,我们知道了另外一个点r,那么我们就可以求出来他们的信息了。

如果我们把s和t放进并查集里,r作为他们的父亲,那么,问题是不是有了新的进展了呢?

显然如果s和t在一个集合里面,那么问题就更容易解决了。(?)
(这里打了一个问号,意为大家看到后面就会明白了。)

设s的根节点为rs,t的根节点为rt,i的根节点为ri

设pre[i]表示前缀和
设sum[i]=pre[i]-pre[ri]

如果s和t在一个集合内,则rs=rt

方便起见,令s = s-1
则sum[t]-sum[s]=pre[t]-pre[rt]-pre[s]+pre[rs] = pre[t]-pre[s]。

即若s和t在一个集合里面,那么他们的区间和就是sum[t]-sum[s],(是不是很妙)
然后我们判断一下这个是否和给出的条件一样就可以判断是否冲突了。

若s和t不在一个集合里面,我们就需要合并他们,我们现在把t合并到s集合上。
即p[rt] = rs。即rrt = rs。

那么sum[rt] = pre[rt]-pre[rrt] = pre[rt]-pre[rs]。
而sum[s] = pre[s]-pre[rs], sum[t] = pre[t]-pre[rt]
即pre[rs] = pre[s]-sum[s], pre[rt] = pre[t]-sum[t].
则pre[rt]-pre[rs] = pre[t]-sum[t]-pre[s]+sum[s]。
又因为pre[t]-pre[s] = v.
则sum[rt] = v-sum[t]+sum[s]。

现在合并的时候的权值关系也理清楚了。
那么这个题目就做完了。

还要注意的一点就是带全并查集的时候find的过程,一定不能乱掉。

int find(int x)
{
	if(p[x]==x) return x;
	int t = find(p[x]);
	sum[x] += sum[p[x]];
	return p[x] = t;
}

代码:

#include <bits/stdc++.h>
#define sc(n) scanf("%d",&n)
#define pt(n) printf("%d\n",n)
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define vi vector<int>
#define vl vector<long long>
#define pb push_back
using namespace std;
const int maxn = 1000;
int p[maxn],sum[maxn];
int n,m;
int find(int x)
{
	if(p[x]==x) return x;
	int t = find(p[x]);
	sum[x] += sum[p[x]];
	return p[x] = t;
}
void unite(int s,int t,int v)
{
	int rs = find(s),rt = find(t);
	sum[rt] = v+sum[s]-sum[t];
	p[rt] = rs;
	find(s);
	find(t);
}
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		bool found = true;
		for(int i=0;i<maxn;i++)
		{
			p[i] = i;
			sum[i] = 0;
		}
		scanf("%d%d",&n,&m);
		for(int i=0;i<m;i++)
		{
			int s,t,v;
			scanf("%d%d%d",&s,&t,&v);
			s--;
			if(find(s)!=find(t))
			{
				unite(s,t,v);
			}
			else
			{
				if(sum[t]-sum[s]!=v) found = false;
			}
		}
		if(found) printf("true\n");
		else printf("false\n");
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值