20191012 练习:差分约束系统

总览:

很套路!
用来处理n元一次不等式组。
把每个未知量看成点,建图,用spfa处理。
(spfa)
约束条件 x i − x j ≤ c k x_i-x_j\leq c_k xixjck变形为 x i ≤ x j + c k x_i\leq x_j+c_k xixj+ck,在图中即单源最短路中的 s [ i ] ≤ s [ j ] + w [ k ] s[i]\leq s[j]+w[k] s[i]s[j]+w[k]
所以 x i − x j ≤ c k x_i-x_j\leq c_k xixjck为j向i连一条长度为 c k c_k ck的有向边。
若图中存在负环,则原方程无解。
对于 x i − x j ≥ c k x_i-x_j\ge c_k xixjck,变形为 x j − x i ≤ − c k x_j-x_i\leq -c_k xjxick即可。
建图时注意建立虚点,用权值为零的有向边指向所有点,以保证图的连通性。

T1 1724:小K的农场

小K的农场
时间限制: 1000 ms 内存限制: 131072 KB
【题目描述】
小K建立了n个农场,他忘记了每个农场中种植作物的具体数量,只记得一些含糊的信息(共m个),以下列三种形式描述:

①农场a比农场b至少多种植了c个单位的作物;

②农场a比农场b至多多种植了c个单位的作物;

③农场a与农场b种植的作物数一样多。

但是,由于小K的记忆有些偏差,所以他想要知道存不存在一种情况,使得农场的种植作物数量与他记忆中的所有信息吻合。

【输入】
第一行包括两个整数 n 和 m,分别表示农场数目和小 K 记忆中的信息数目。

接下来 m 行:

如果每行的第一个数是 1,接下来有 3 个整数 a,b,c,表示农场 a 比农场 b 至少多种植了c个单位的作物。

如果每行的第一个数是2,接下来有3个整数a,b,c,表示农场a比农场b至多多种植了c个单位的作物。如果每行的第一个数是3,接下来有2个整数a,b,表示农场a种植的的数量和农场b一样多。

【输出】
如果存在某种情况与小 K 的记忆吻合,输出“Yes”,否则输出“No”。

【输入样例】
3 3
3 1 2
1 1 3 1
2 2 3 2
【输出样例】
Yes
【提示】
【数据规模】

对于 100% 的数据保证: 1 ≤ n , m , a , b , c ≤ 10000 1≤n,m,a,b,c≤10000 1n,m,a,b,c10000

思路:列出不等式,以0为虚点,判负环即可
注:x,y的作物数相等即x连一条权值为零的有向边向y,y连一条权值为零的有向边向x。
注:要用dfs版spfa,不然要TLE。

程序:

#include<bits/stdc++.h>
using namespace std;

const int A=1e4+5;

int n,m;
int u,v;

struct gg
{
	int to,w;
};

gg a;

vector <gg> ljb[A];

int s[A],num[A];
bool ex[A],boom;

void spfa(int x)
{
	ex[x]=1;
	for(int y=0;y<ljb[x].size();y++)
	{
		if(boom==1)	return;
		gg z=ljb[x][y];
		if(s[z.to]>s[x]+z.w)
		{
			s[z.to]=s[x]+z.w;
			if(ex[z.to]==0)	spfa(z.to);
			else
			{
				boom=1;
				return;
			}
		}
	}
	ex[x]=0;
}

int main()
{
	scanf("%d%d",&n,&m);
	fill(s,s+1+n,1e8);
	for(int i=1;i<=m;i++)
	{
		scanf("%d",&u);
		if(u==1)
		{
			scanf("%d%d%d",&v,&a.to,&a.w);
			a.w*=-1;
			ljb[v].push_back(a);
		}
		if(u==2)
		{
			scanf("%d%d%d",&a.to,&v,&a.w);
			ljb[v].push_back(a);
		}
		if(u==3)
		{
			scanf("%d%d",&v,&a.to);
			a.w=0;
			ljb[v].push_back(a);
			swap(a.to,v);
			ljb[v].push_back(a);
		}
	}
	for(int i=1;i<=n;i++)
	{
		a.to=i,a.w=0;
		ljb[0].push_back(a);
	}
	s[0]=0;
	spfa(0);
	if(boom==1)	printf("No");
	else	printf("Yes");
	return 0;
}

T2 1725:账本核算

账本核算
时间限制: 1000 ms 内存限制: 165888 KB
【题目描述】
小D接到一个任务,为税务部门调查一位商人的账本,看看账本是不是伪造的。账本上记录了n个月以来的收入情况,其中第i个月的收入额为Ai(i=1,2,3,…,n−1,n)。当 Ai大于0时表示这个月盈利Ai元,当 Ai小于0时表示这个月亏损Ai元。所谓一段时间内的总收入,就是这段时间内每个月的收入额的总和。小D的任务是秘密进行的,每次查看账本时她都只能看某段时间内账本上记录的收入情况,并且她只能记住这段时间内的总收入。现在,小D总共查看了m次账本,当然也就记住了m段时间内的总收入,你的任务是根据记住的这些信息来判断账本是不是假的。

【输入】
第一行为一个正整数T,表示有T组数据,即T个账本,需要你判断。对于每组数据:

第一行为两个正整数n和m,分别表示对应的账本记录了多少个月的收入情况以及查看了多少次账本。

接下来m行表示小D查看m次账本后记住的m条信息,每条信息占一行,有三个整数x,y和w,表示从第x个月到第y个月(包含第y个月)的总收入为w。

【输出】
包含T行,每行是true或false,其中第i行为true当且仅当第i组数据,即第i个账本不是假的;第i行为false当且仅当第i组数据,即第i个账本是假的。

【输入样例】
2
3 3
1 2 10
1 3 -5
3 3 -15
5 3
1 5 100
3 5 50
1 2 51
【输出样例】
true
false
【提示】
【数据规模及约定】

对于100%的数据,满足T<100,n,m<100,1≤x≤y≤n。

思路:
求前缀和,列出等式 s [ y ] − s [ x − 1 ] = w s[y]-s[x-1]=w s[y]s[x1]=w,转换为不等式 s [ y ] − s [ x − 1 ] ≤ w , s [ y ] − s [ x − 1 ] ≥ w s[y]-s[x-1]\le w,s[y]-s[x-1]\ge w s[y]s[x1]w,s[y]s[x1]w求解即可。
注:因为要求前缀和,所以不能以0为虚点,要以n+1为虚点。

程序:

#include<bits/stdc++.h>
using namespace std;

const int A=105;

int t;
int n,m;
int u;
int s[A];
bool ex[A],boom;

struct gg
{
	int to,w;
};

gg a;

vector <gg> ljb[A];

void spfa(int x)
{
	ex[x]=1;
	for(int y=0;y<ljb[x].size();y++)
	{
		if(boom)	return;
		gg z=ljb[x][y];
		if(s[z.to]>s[x]+z.w)
		{
			s[z.to]=s[x]+z.w;
			if(!ex[z.to])	spfa(z.to);
			else
			{
				boom=1;
				return;
			}
		}
	}
	ex[x]=0;
}

void clean()
{
	boom=0;
	memset(ex,0,sizeof(ex));
	for(int i=0;i<=n;i++)
		ljb[i].clear();
}

int main()
{
	scanf("%d",&t);
	while(t--)
	{
		clean();
		scanf("%d%d",&n,&m);
		fill(s,s+n,1e8);
		for(int i=1;i<=m;i++)
		{
			scanf("%d%d%d",&u,&a.to,&a.w);
			u--;
			ljb[u].push_back(a);
			swap(u,a.to);
			a.w*=-1;
			ljb[u].push_back(a);
		}
		for(int i=0;i<=n;i++)
		{
			a.to=i,a.w=0;
			ljb[n+1].push_back(a);
		}
		s[n+1]=0;
		spfa(n);
		if(boom==1)	printf("false\n");
		else	printf("true\n");
	}
	return 0;
}

T3 Intervals(Poj1201/TYVJ1415)

Intervals
时间限制: 1000 ms 内存限制: 65536 KB
【题目描述】
原题来自:Southwestern Europe 2002,题面可参考 POJ 1201。

给定 n 个闭区间 [ a i , b i ] [a_i,b_i] [ai,bi]和 n 个整数 c i c_i ci。你需要构造一个整数集合 Z Z Z,使得对于任意 i ∈ [ 1 , n ] i∈[1,n] i[1,n] Z Z Z中满足 a i ≤ x ≤ b i a_i≤x≤b_i aixbi的整数 x 不少于 c i c_i ci 个,求这样的整数集合 Z Z Z 最少包含多少个数.

简而言之就是,从 0 ∼ 5 × 1 0 4 0∼5×10^4 05×104 中选出尽量少的整数,使每个区间 [ai,bi]内都有至少 c i c_i ci个数被选出。

【输入】
第一行一个整数 n,表示区间个数;

以下 n 行每行描述这些区间,第 i+1 行三个整数 a i , b i , c i a_i,b_i,c_i ai,bi,ci ,由空格隔开。

【输出】
一行,输出满足要求的序列最少整数个数。

【输入样例】
5
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1
【输出样例】
6
【提示】
数据范围与提示
对于全部数据, 1 ≤ n ≤ 5 × 1 0 4 , 0 ≤ a i ≤ b i ≤ 5 × 104 , 1 ≤ c i ≤ b i − a i + 1 1≤n≤5×10^4,0≤ai≤bi≤5×104,1≤ci≤bi−ai+1 1n5×104,0aibi5×104,1cibiai+1

思路:
与T2类似。
先求个数的前缀和,列出不等式 s [ i ] − s [ j − 1 ] ≥ w s[i]-s[j-1]\ge w s[i]s[j1]w,求解即可。
注意,为保证图的连通性,相邻两点间需连边,有不等式 s [ a + 1 ] − s [ a ] ≤ 1 s[a+1]-s[a]\le 1 s[a+1]s[a]1, s [ a + 1 ] − s [ a ] ≥ 0 s[a+1]-s[a]\ge 0 s[a+1]s[a]0

程序:

#include<bits/stdc++.h>
using namespace std;

const int A=5e4+10;

int n;
int u;
int s[A],maxx;
bool ex[A];

struct gg
{
	int to,w;
};

gg a;

vector <gg> ljb[A];

queue <int> q;

void spfa(int begin)
{
	s[begin]=0;
	ex[begin]=1;
	q.push(begin);
	while(!q.empty())
	{
		int x=q.front();
		ex[x]=0;
		q.pop();
		for(int y=0;y<ljb[x].size();y++)
		{
			gg z=ljb[x][y];
			if(s[z.to]>s[x]+z.w)
			{
				s[z.to]=s[x]+z.w;
				if(!ex[z.to])
				{
					ex[z.to]=1;
					q.push(z.to);
				}
			}
		}
	}
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d%d",&a.to,&u,&a.w);
		maxx=max(maxx,u);
		a.to--,a.w*=-1;
		ljb[u].push_back(a);
	}
	fill(s,s+maxx+1,1e8);
	for(int i=1;i<=maxx;i++)
	{
		u=i-1,a.to=i,a.w=1;
		ljb[u].push_back(a);
		swap(u,a.to);
		a.w=0;
		ljb[u].push_back(a);
	}
	spfa(maxx);
	printf("%d",abs(s[0]));
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值