【BZOJ3509】【CodeChef】COUNTARI
【BZOJ1229】【USACO2008 Nov】toy 玩具

【BZOJ1221】【HNOI2001】软件开发

Zarxdy34 posted @ 2016年2月28日 16:31 in BZOJ with tags 费用流 , 529 阅读

  这题和线性规划与网络流24题里的餐巾计划问题基本一样,除了这里的餐巾洗了x天后第x+1天才能投入使用。

  一直对这类建模理解的不是很透彻。(还有题目叫软件开发什么鬼)

  将第i天用完的毛巾和需要用的毛巾分成Xi,Yi两个点集处理。

  每天用完的毛巾有Ni条,从S向Xi连一条流量为Ni,费用为0的边来约束;

  每天需要用Ni条毛巾,从Yi向T连一条流量为Ni,费用为0的边来约束;

  每天用完的毛巾一部分送去快洗,从Xi向Yi+n+a+1连一条流量为inf,费用为fA的边;

  还有送去慢洗的,从Xi向Yi+n+b+1连一条流量为inf,费用为fB的边;

  还有留着先不处理留到第二天的,从Xi向Xi+1连一条流量为inf,费用为fB的边;

  每天可能需要新买毛巾,从S向Yi连一条流量为inf,费用为f的边。

  建图建完做一遍最小费用最大流就好了。

 

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=2010,maxe=maxn*6,inf=~0U>>1;

int head[maxn],next[maxe],E[maxe],F[maxe],C[maxe],Ecnt;
int n,a,b,f,fA,fB;
int S,T;

void Add_Edge(int x,int y,int flow,int cost)
{
	next[++Ecnt]=head[x];head[x]=Ecnt;E[Ecnt]=y;F[Ecnt]=0;C[Ecnt]=cost;
	next[++Ecnt]=head[y];head[y]=Ecnt;E[Ecnt]=x;F[Ecnt]=flow;C[Ecnt]=-cost;
}

int vis[maxn];
int q[maxn],qhead,qtail;
int dis[maxn];

int inc(int x) {return (x+1)%maxn;}
int dec(int x) {return (x-1+maxn)%maxn;}

bool Spfa(int st,int ed)
{
	memset(vis,0,sizeof(vis));
	memset(dis,0x7F,sizeof(dis));
	q[qhead=qtail=0]=st;
	vis[st]=1;dis[st]=0;
	while (qhead!=inc(qtail))
	{
		int x=q[qhead];
		qhead=inc(qhead);
		vis[x]=0;
		for (int i=head[x];i;i=next[i]) if (F[i] && dis[E[i]]>dis[x]-C[i])
		{
			dis[E[i]]=dis[x]-C[i];
			if (!vis[E[i]])
				if (qhead!=inc(qtail) && dis[q[qhead]]>dis[E[i]]) q[qhead=dec(qhead)]=E[i];else q[qtail=inc(qtail)]=E[i];
			vis[E[i]]=1;
		}
	}
	return dis[ed]!=0x7F7F7F7F;
}

int DFS(int x,int flow,int &res)
{
	vis[x]=1;
	if (x==T) return flow;
	int rest=flow;
	for (int i=head[x];i && rest;i=next[i])
		if (dis[E[i]]==dis[x]-C[i] && F[i^1] && !vis[E[i]])
		{
			int d=DFS(E[i],min(rest,F[i^1]),res);
			res+=d*C[i];
			rest-=d;F[i]+=d;F[i^1]-=d;
		}
	return flow-rest;
}

void Min_Cost_Flow()
{
	int res=0;
	while (Spfa(T,S))
	{
		vis[T]=1;
		while (vis[T])
		{
			memset(vis,0,sizeof(vis));
			DFS(S,inf,res);
		}
	}
	printf("%d\n",res);
}

int main()
{
	scanf("%d%d%d%d%d%d",&n,&a,&b,&f,&fA,&fB);
	S=n*2+1;T=S+1;Ecnt=1;
	for (int i=1;i<=n;i++)
	{
		if (i+1<=n) Add_Edge(i,i+1,inf,0);
		if (i+a+1<=n) Add_Edge(i,i+a+n+1,inf,fA);
		if (i+b+1<=n) Add_Edge(i,i+b+n+1,inf,fB);
		Add_Edge(S,i+n,inf,f);
		int x;scanf("%d",&x);
		Add_Edge(S,i,x,0);
		Add_Edge(i+n,T,x,0);
	}
	Min_Cost_Flow();
	return 0;
}

 

  事实上这道题是可以用求单峰函数极值做的。

  具体做法来源见BZOJ1229,下面贴个代码。

 

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=1010,inf=~0U>>1;

int n,N1,N2,C1,C2,Tc;
int a[maxn],asum;
int ans;

int q[maxn],top1,top2;
int fa[maxn];

int Find(int x) {return (fa[x]==x)?x:fa[x]=Find(fa[x]);}

int f(int x)
{
	for (int i=1;i<=n;i++) q[i]=a[i],fa[i]=i;
	top1=1;top2=0;
	int res=Tc*x;
	for (int i=1;i<=n;i++)
	{
		top2=i-N1;
		if (x<a[i])
		{
			while (top1<=i-N2 && x<a[i])
			{
				int m=min(q[top1],a[i]-x);
				x+=m;
				q[top1]-=m;
				res+=C2*m;
				if (x<a[i]) top1++; 
			}
			while (top2>=top1 && x<a[i])
			{
				int m=min(q[top2],a[i]-x);
				x+=m;
				q[top2]-=m;
				res+=C1*m;
				if (x<a[i]) fa[top2]=Find(top2-1),top2=fa[top2];
			}
			if (x<a[i]) return inf;
		}
		x-=a[i];
	}
	return res;
}

int main()
{
	scanf("%d%d%d%d%d%d",&n,&N1,&N2,&Tc,&C1,&C2);
	N1++;N2++;
	if (N1>N2) swap(N1,N2),swap(C1,C2);
	if (C1<C2) C2=C1;
	for (int i=1;i<=n;i++) scanf("%d",&a[i]),asum+=a[i];
	int l=0,r=asum;
	while (r-l>2)
	{
		int m1=l+(r-l)/3,m2=l+2*((r-l)/3);
		int fm1=f(m1),fm2=f(m2);
		if (fm1!=inf && fm1<=fm2) r=m2;else l=m1;
	}
	ans=inf;
	for (int i=l;i<=r;i++) ans=min(ans,f(i));
	printf("%d\n",ans);
	return 0;
}

登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter