【HDU5545 2015 CCPC 南阳国赛F】【差分约束思想 费用流思想】The Battle of Guandu 官渡之战 重要战场人数多 士兵流向转化问题为最短路spfa+dijkstra双写

#include<stdio.h>
#include<string.h>
#include<ctype.h>
#include<math.h>
#include<iostream>
#include<string>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre(){freopen("c://test//input.in","r",stdin);freopen("c://test//output.out","w",stdout);}
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1,class T2>inline void gmax(T1 &a,T2 b){if(b>a)a=b;}
template <class T1,class T2>inline void gmin(T1 &a,T2 b){if(b<a)a=b;}
const int N=1e5+10,M=1e5+10,Z=1e9+7,ms63=1061109567;
int casenum,casei;
int n,m;
int x[N],y[N],cc[N];
int ww[M];
int first[M],id;
int w[N],c[N],nxt[N];
LL f[M];
bool e[M];
void ins(int x,int y,int z)
{
	id++;
	w[id]=y;
	c[id]=z;
	nxt[id]=first[x];
	first[x]=id;
}
struct node
{
	int x;LL v;
	node(){}
	node(int x_,LL v_){x=x_;v=v_;}
	bool operator < (const node& b)const {return v>b.v;}
};
priority_queue<node>q;
void inq(int x,LL v)
{
	if(v>=f[x])return;
	f[x]=v;
	q.push(node(x,v));
}
LL dijkstra()
{
	for(int i=1;i<=n;i++)
	{
		ins(y[i],x[i],cc[i]);
		if(ww[y[i]]==0)inq(y[i],0);
	}
	while(!q.empty())
	{
		int x=q.top().x;q.pop();
		if(e[x])continue;e[x]=1;
		for(int z=first[x];z;z=nxt[z])inq(w[z],f[x]+c[z]);
	}
	LL ans=0;
	for(int i=1;i<=m;i++)
	{
		if(ww[i]==2)
		{
			if(f[i]==1e12)return -1;
			ans+=f[i];
		}
	}
	return ans;
}
const int L=1e6;
int Q[L],h,t;
void inQ(int x,LL v)
{
	if(v>=f[x])return;
	f[x]=v;
	if(e[x])return;
	e[x]=1;
	Q[t++]=x;
}
LL spfa()
{
	h=t=0;
	for(int i=1;i<=n;i++)
	{
		ins(y[i],x[i],cc[i]);
		if(ww[y[i]]==0)inQ(y[i],0);
	}
	while(h<t)
	{
		int x=Q[h++];e[x]=0;
		for(int z=first[x];z;z=nxt[z])inQ(w[z],f[x]+c[z]);
	}
	LL ans=0;
	for(int i=1;i<=m;i++)
	{
		if(ww[i]==2)
		{
			if(f[i]==1e12)return -1;
			ans+=f[i];
		}
	}
	return ans;
}
int main()
{
	scanf("%d",&casenum);
	for(casei=1;casei<=casenum;casei++)
	{
		scanf("%d%d",&n,&m);
		for(int i=1;i<=m;i++)
		{
			first[i]=0;
			f[i]=1e12;
			e[i]=0;
		}id=0;
		for(int i=1;i<=n;i++)scanf("%d",&x[i]);
		for(int i=1;i<=n;i++)scanf("%d",&y[i]);
		for(int i=1;i<=n;i++)scanf("%d",&cc[i]);
		for(int i=1;i<=m;i++)scanf("%d",&ww[i]);

		//首先是dijkstra写法
		//printf("Case #%d: %lld\n",casei,dijkstra());

		//然后是SPFA写法
		printf("Case #%d: %lld\n",casei,spfa());
	}
	return 0;
}
/*
【trick&&吐槽】
有1e5个点,边权也为1e5,是可能爆int的。

【题意】
有T(30)组数据。
对于每组数据有n(1e5)个村庄,m(1e5)个战场
对于村庄i,曹操可以选择支付c[i]*num元,(0<=c[i]<=1e5)
使得这个村庄派出num个士兵,在战场x[i]为曹操作战,在战场y[i]为袁绍作战。(1<=x[i],y[i]<=m)

对于每个战场,都有个战略价值(0,1,2)。
在战略价值为2的战场,曹操在该战场的士兵数必须严格比袁绍多
在战略价值为1的战场,曹操在该战场的士兵数必须不能比袁绍少(按照贪心原则,实际一定会相等)
在战略价值为0的战场,曹操在该战场的士兵数无所谓(按照贪心原则,实际一定会为0)

让你输出保障上述条件曹操至少需要花费的金钱。
如果无法保障这个条件,则输出-1。

【类型】
差分约束思想
网络流思想
最短路。

【分析】
首先有一个很显然的贪心——
如果一个战场的战略价值为2,那么在这个战场,恰有:曹兵-袁兵=1
如果一个战场的战略价值为1,那么在这个战场,恰有:曹兵=袁兵=0

这题我们可以从村庄向两个战场连边。
但是——如何更加简单地设置关系呢?
为何不考虑直接从曹战场向袁战场连边呢?

这题设计到分配,尽管数据巨大不能用网络流做,我们还是可以考虑先简化数据规模,引入思想。
我们对于每个村庄所提供的(x,y,z),从曹战场x向袁战场y连接一条流量无穷,费用为z的边。

然后,我们有一个关键性的问题就是,如何保证题目的要求呢?
首先我们简化问题,只去考虑合法性。
对于所有战略价值为2的战场,如果从这个样的点ST出发,沿着图,能够达到任意一个战略价值为0的点。
那说明,这个战略价值为2的战场,可以使得其上的曹兵比袁兵多。
为什么能说明这个呢?
很显然,我们对于一条边,使得ST的曹兵+1,下一个点的袁兵+1。
但是这样,对于下一个点是有影响的。
如果下个点重要度为0,显然这样已经可以了。
如果下个点重要度为1,我们还要继续把这1个人转移出去,一直转移到重要度为0的点上即可。
如果下个点重要度为2,我们依然一定要把这1个人转移出去。而只要转移出去了,下个点实际上其实并没有受到影响。

于是,我们枚举所有重要度为2的点,每个点沿着这个图转移一个人到重要度为0的战场上,这道题就可以AC了。
体现在网络流上,就是:
[超级源点->重要度为2的点,流量为1,费用为0]
[曹战场->袁战场,流量无限,费用为人的雇佣成本]
[重要度为0的点->超级汇点,流量无限,费用为0]
跑一个最小费用最大流,如果最大流=重要度为2的点数,那么这个最小费用就是答案了。

只是时间复杂度不允许我们跑最小费用最大流。
然而我们发现,基于这道题的特殊性,其实直接求:

枚举每个重要度为2的点,
对于每个这样的点,求它到重要度为0的点中距离最近的那个的距离。
然后这些距离求和即可。

但是这样是多源最短路。
然而我们发现,只要把边逆过来,初始化所有重要度为0的点为起点,都归为ZERO,求最短路。
然后累加所有重要度为2的点的到 ZERO的最短路即可。

【时间复杂度&&优化】
dijkstra O(nlogn)
SPFA O(kn)

*/


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值