费用流
餐巾计划问题

这建图太厉害了,我想了巨久都没想到怎么控制流量先合法又能流到别的地方去。

还是思维太烂了qaq

用了一个把流量拆开的思路,就叫Ta拆流吧

具体的说,我们对一个点,有一个流量一定要流,但是这个流量也可以给后面的用,我们就把这个流拆开。

对应此题,先把点拆成\(i\)\(i+n\),然后\(i\)连接\(t\)表示流量限制,费用为\(0\),要流满,这也是整个图唯一连接\(t\)的边。

然后\(i+n\)表示当天用完的这个脏餐巾的流,这个流可以给下一天的脏餐巾,可以送去快洗慢洗再给对应的\(j\)号点代表\(j\)得到流

然后建一下买餐巾的流就可以了


Code:

#include <cstdio>
#include <cstring>
#include <cctype>
#include <queue>
#include <algorithm>
using std::min;
#define ll long long
template <class T>
void read(T &x)
{
	x=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) x=x*10+c-'0',c=getchar();
}
const int N=4010;
const int M=1e5;
const int inf=0x3f3f3f3f;;
int head[N],to[M],Next[M],edge[M],cost[M],cnt=1;
void add(int u,int v,int w,int c)
{
	to[++cnt]=v,edge[cnt]=w,cost[cnt]=c,Next[cnt]=head[u],head[u]=cnt;
	to[++cnt]=u,edge[cnt]=0,cost[cnt]=-c,Next[cnt]=head[v],head[v]=cnt;
}
int n,need[N],s,t;
int used[N],dis[N],pre[N];
bool spfa()
{
	memset(dis,0x3f,sizeof dis);
	dis[s]=0;
	std::queue <int> q;
	q.push(s);
	while(!q.empty())
	{
		int now=q.front();
		q.pop();
		used[now]=0;
		for(int v,i=head[now];i;i=Next[i])
			if(edge[i]&&dis[v=to[i]]>dis[now]+cost[i])
			{
				dis[v]=dis[now]+cost[i];
				pre[v]=i;
				if(!used[v]) q.push(v),used[v]=1;
			}
	}
	return dis[t]<dis[0];
}
int main()
{
	read(n);
	for(int i=1;i<=n;i++) read(need[i]);
	int t1,t2,p0,p1,p2;
	read(p0),read(t1),read(p1),read(t2),read(p2);
	s=n<<1|1,t=s+1;
	for(int i=1;i<=n;i++)
	{
		add(s,i,inf,p0);
		add(i,t,need[i],0);
		add(s,i+n,need[i],0);
		if(i<n) add(i+n,i+1+n,inf,0);
		if(i+t1<=n) add(i+n,i+t1,inf,p1);
		if(i+t2<=n) add(i+n,i+t2,inf,p2);
	}
	ll ans=0;
	while(spfa())
	{
		int mi=inf,now=t;
		while(pre[now])
		{
			mi=min(mi,edge[pre[now]]);
			now=to[pre[now]^1];
		}
		ans+=mi*dis[t];now=t;
		while(pre[now])
		{
			edge[pre[now]]-=mi;
			edge[pre[now]^1]+=mi;
			now=to[pre[now]^1];
		}
	}
	printf("%lld\n",ans);
	return 0;
}

2019.2.26