【VIJOS】1891学姐的逛街计划-费用流

传送门:vijos1891


题解

x i = 1 / 0 x_i=1/0 xi=1/0分别表示第 i i i天请假/不请假,存在不等式:
x 1 + x 2 + . . . + x n ≤ k x_1+x_2+...+x_n\leq k x1+x2+...+xnk
x 2 + x 3 + . . . + x n + 1 ≤ k x_2+x_3+...+x_{n+1}\leq k x2+x3+...+xn+1k
⋮ \vdots
x 2 n + 1 + . . . + x 3 n ≤ k x_{2n+1}+...+x_{3n}\leq k x2n+1+...+x3nk

增加调节变量:
x 1 + x 2 + . . . + x n + y 1 = k x_1+x_2+...+x_n+y_1=k x1+x2+...+xn+y1=k
x 2 + x 3 + . . . + x n + 1 + y 2 = k x_2+x_3+...+x_{n+1}+y_2=k x2+x3+...+xn+1+y2=k
⋮ \vdots
x 2 n + 1 + . . . + x 3 n + y 2 n + 1 = k x_{2n+1}+...+x_{3n}+y_{2n+1}=k x2n+1+...+x3n+y2n+1=k

上式-下式:
x 1 + y 1 = x n + 1 + y 2 x_1+y_1=x_{n+1}+y_2 x1+y1=xn+1+y2
⋮ \vdots
x 2 n + y 2 n = x 3 n + y 2 n + 1 x_{2n}+y_{2n}=x_{3n}+y_{2n+1} x2n+y2n=x3n+y2n+1

另有:
k = x 1 + x 2 + . . . + x n k=x_1+x_2+...+x_n k=x1+x2+...+xn
x 2 n + 1 + . . . + x 3 n = k x_{2n+1}+...+x_{3n}=k x2n+1+...+x3n=k

发现每个变量都分别在等式左右出现了一次。

类似于网络流流入流出:
于是将等号看做点,等式左边看做流入流量,右边看做流出流量。两个 k k k分别看做源汇点,跑最小费用最大流即可。


代码

#include<bits/stdc++.h>
using namespace std;
const int N=6e5+100;
const int inf=2e9;
int n,ky,S,T,tot=1,a[650],vis[2020],dis[2020];
int head[2020],to[N],nxt[N],w[N],ct[N],cost;
deque<int>Q;
char c;

template<class TT>
inline void rd(TT &x)
{
	c=getchar();x=0;int f=0;
	for(;!isdigit(c);c=getchar()) if(c=='-') f=1;
	for(;isdigit(c);c=getchar()) x=x*10+(c^48);
    if(f) x=-x;
}

inline void lk(int u,int v,int vv,int cc){
  to[++tot]=v;nxt[tot]=head[u];head[u]=tot;w[tot]=vv;ct[tot]=cc;
  to[++tot]=u;nxt[tot]=head[v];head[v]=tot;w[tot]=0;ct[tot]=-cc;
}

inline bool spfa()
{
	int i,j,x;
	for(i=1;i<T;++i) dis[i]=-inf,vis[i]=0;
	vis[T]=1;dis[T]=0;Q.push_back(T);
	while(!Q.empty()){
		x=Q.front();Q.pop_front();
		for(i=head[x];i;i=nxt[i]){
			j=to[i];if(!w[i^1] || dis[j]>=dis[x]-ct[i]) continue;
			dis[j]=dis[x]-ct[i];
			if(!vis[j]){
				if(Q.empty() || dis[Q.front()]>=dis[j]) Q.push_back(j);
				else Q.push_front(j);
			}
		}
		vis[x]=0;
	}	
	return dis[S]>-inf;
}

inline int dfs(int x,int f)
{
	vis[x]=1;
	if(x==T) return f;
	int i,j,res,ss=0;
	for(i=head[x];i;i=nxt[i]){
		j=to[i];if(vis[j] || (!w[i]) || dis[j]!=dis[x]-ct[i]) continue;
		res=dfs(j,min(f-ss,w[i]));if(!res) continue;
		ss+=res;w[i]-=res;w[i^1]+=res;cost+=res*ct[i];
		if(ss==f) return f;
	}
	if(!ss) dis[x]=-inf;
	return ss;
}

int main(){
	int i,j,ix,iy,iz,iu,lim;
	rd(n);rd(ky);
	lim=(n<<1)+n;
	for(i=1;i<=lim;++i) rd(a[i]);
	S=(n<<1)+3;T=S+1;
	for(i=1;i<=n;++i) lk(i,i+n,1,a[i+n]);
	for(ix=(n<<1)+1,i=1;i<=n;++i) lk(ix,i,1,a[i]);
	for(lim=(n<<1),ix++,i=n+1;i<=lim;++i) lk(i,ix,1,a[n+i]);
	for(i=1;i<lim;++i) lk(i,i+1,ky,0);
    lk(lim+1,1,ky,0);lk(lim,lim+2,ky,0);
    lk(S,lim+1,ky,0);lk(lim+2,T,ky,0);
    for(;spfa();){
    	for(vis[T]=1;vis[T];){
    		for(i=1;i<=T;++i) vis[i]=0;
    		dfs(S,inf);
    	}
    }
    printf("%d\n",cost);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值