UVA 11478 - Halum 差分约束

http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=9

题目大意:

给定一个有向图,每条边都有一个权值,每次你可以选择一个结点v和整数d,把所有以v为终点的边权值减少d,把所有以v为起点的边权值增加d,最后要让所有的边权值非负且最大。

思路:

为了做这题前面先做了好几题差分约束的。。。。

作者思路很巧妙:

不同的操作互不影响,因此可以按任意的顺序实施这些操作,另外,对于同一个结点的多次操作也可以合并,因此令sum(u)为作用于结点u上的所有d之和。这样,本题的目标就是确定所有的sum(u),使得操作之后所有边权值的最小值尽量大。

“最小值最大”使用二分答案的方法。二分答案x之后,问题转化为是否可以让操作完毕后每条边的权值均不小于x。对于边a->b,不难发现操作完毕后它的权值为w(a,b)+sum(a)-sum(b),因此每条边a->b都可以列出一个不等式w(a,b)+sum(a)-sum(b)>=x,移项得sum(b)-sum(a)<=w(a,b)-x。这样,我们实际得到一个差分约束系统。

查分约束系统是指一个不等式组,每个不等式形如xj-xi<=bk,这里的bk是一些事先已知的常数。这个不等式类似于最短路中的不等式d[v]<=d[u]+w(u,v),我们可以用最短路算法求解:对于约束条件xj-xi《=bk,新建一条边i-->j,权值为bk。如果图中有负权环,则差分约束系统无解。

PS:用超级源点的话2.7S,如果不用超级源点,把所有点加入队列那么2.1S。。。以后我还是不用超级源点吧。



#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int MAXN=500+10;
const int MAXM=2700+10;
const int INF=100000+10;
int head[MAXN],len,dis[MAXN],vis[MAXN],cnt[MAXN],n,m;
struct edge
{
	int to,val,next;
}e[MAXM];
void add(int from,int to,int val)
{
	e[len].to=to;
	e[len].val=val;
	e[len].next=head[from];
	head[from]=len++;
}

bool spfa()
{
	memset(vis, 0, sizeof(vis));
    memset(cnt, 0, sizeof(cnt));

	queue<int> q;
	for(int i = 0; i <=n; i++) { dis[i] = 0;q.push(i); }
	while(!q.empty())
	{
		int cur=q.front();
		q.pop();
		vis[cur]=false;
		for(int i=head[cur];i!=-1;i=e[i].next)
		{
			int id=e[i].to;
			if(dis[id] > dis[cur]+e[i].val)
			{
				dis[id]=dis[cur]+e[i].val;
				if(!vis[id])
				{
					if(++cnt[id] >n)
						return false;
					vis[id]=true;
					q.push(id);
				}
			}
		}
	}
	return true;
}
bool change(int x)
{
	for(int k=1;k<=n;k++)
		for(int i=head[k];i!=-1;i=e[i].next)
			e[i].val-=x;

	bool ok=spfa();

	for(int k=1;k<=n;k++)
		for(int i=head[k];i!=-1;i=e[i].next)
			e[i].val+=x;
	return ok;
}
int main()
{
	while(~scanf("%d%d",&n,&m))
	{
		memset(head,-1,sizeof(head));
		len=0;
	//	for(int i=1;i<=n;i++)
	//		add(0,i,0);
		int L=1,R=0;
		for(int i=0;i<m;i++)
		{
			int from,to,val;
			scanf("%d%d%d",&from,&to,&val);
			add(from,to,val);
			if(val > R)  R=val;
		}
		
		
		if(change(R))
		{
			puts("Infinite");
			continue;
		}
		else if(!change(L))
		{
			puts("No Solution");
			continue;
		}
		
		int ans=L++;
		while(L<R)
		{
			int mid=L+((R-L)>>1);
			if(!change(mid))
				R=mid;
			else		
			{
					L=mid+1;
					ans=mid;
			}
		}
		printf("%d\n",ans);
	}
	return 0;
}


超级源点。。。以后差分约束不用了- -

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int MAXN=500+10;
const int MAXM=27000;
const int INF=100000+10;
int head[MAXN],len,dis[MAXN],vis[MAXN],cnt[MAXN],n,m;
struct edge
{
	int to,val,next;
}e[MAXM];
void add(int from,int to,int val)
{
	e[len].to=to;
	e[len].val=val;
	e[len].next=head[from];
	head[from]=len++;
}

bool spfa()
{
	for(int i=1;i<=n;i++)
	{
		vis[i]=false;
		dis[i]=INF;
		cnt[i]=0;
	}
	queue<int> q;
	q.push(0);
	cnt[0]++;
	dis[0]=0;
	vis[0]=true;
	while(!q.empty())
	{
		int cur=q.front();
		q.pop();
		vis[cur]=false;
		for(int i=head[cur];i!=-1;i=e[i].next)
		{
			int id=e[i].to;
			if(dis[id] > dis[cur]+e[i].val)
			{
				dis[id]=dis[cur]+e[i].val;
				if(!vis[id])
				{
					if(++cnt[id] >n)
						return false;
					vis[id]=true;
					q.push(id);
				}
			}
		}
	}
	return true;
}
bool change(int x)
{
	for(int k=1;k<=n;k++)
		for(int i=head[k];i!=-1;i=e[i].next)
			e[i].val-=x;

	bool ok=spfa();

	for(int k=1;k<=n;k++)
		for(int i=head[k];i!=-1;i=e[i].next)
			e[i].val+=x;
	return ok;
}
int main()
{
	while(~scanf("%d%d",&n,&m))
	{
		memset(head,-1,sizeof(head));
		len=0;
		for(int i=1;i<=n;i++)
			add(0,i,0);
		int L=1,R=0;
		for(int i=0;i<m;i++)
		{
			int from,to,val;
			scanf("%d%d%d",&from,&to,&val);
			add(from,to,val);
			if(val > R)  R=val;
		}
		
		
		if(change(R))
		{
			puts("Infinite");
			continue;
		}
		else if(!change(L))
		{
			puts("No Solution");
			continue;
		}
		
		int ans=L++;
		while(L<R)
		{
			int mid=L+((R-L)>>1);
			if(!change(mid))
				R=mid;
			else		
			{
					L=mid+1;
					ans=mid;
			}
		}
		printf("%d\n",ans);
	}
	return 0;
}



转载于:https://www.cnblogs.com/murmured/p/5004125.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值