[NOI2012]美食节

美食节

题解

其实还是很简单的。

看到这道题应该很容易想到费用流。
由于做菜有时间顺序,我们考虑将每个厨师的每一道菜单独建点。对于 i i i号厨师的导师第 j j j号菜,向汇点连一条流量为1边权为0的边,限制这样的菜只能由一个。再将所有菜连向这个点,对与菜 k k k边权为 j ⋅ t i , k j\cdot t_{i,k} jti,k,表示第 k k k道菜的价值。
至于每道菜的需求,只需要向这道菜连它需求的流量即可,保证最多只会有这么多道菜被做。

但很明显,虽然这样的做法可以得到答案,但图还是太大了,明显会T,考虑优化。
我们明显不能将每个厨师的每一道菜都建出来,因为总菜数只有那么多,而这样做会有许多点根本没用到。
所以我们考虑动态加点。很明显,EK每次增广路径有且只会增广一条路径,经过一道菜。
我们可以在每次增广路径上找到增广的菜,单独给做这道菜的厨师给它拓展一道菜,再跑增广路径。因为只有这个厨师的下一道菜会给下一次操作产生影响,其他厨师这道菜都没做,显然不会直接做下一道菜。
所以我们只需要在每次增广完后动态加点,当不能再增广时退出即可。

这样一来总点数为 ∑ i = 1 n p i + n + m \sum_{i=1}^{n}p_{i}+n+m i=1npi+n+m,边数为 n ( ∑ i = 1 n p i + m ) n(\sum_{i=1}^{n}p_{i}+m) n(i=1npi+m)。完全足够。
时间复杂度 O ( ( ∑ i = 1 n p i + n + m ) 3 ) O\left((\sum_{i=1}^{n}p_{i}+n+m)^3\right) O((i=1npi+n+m)3)?反正跑不满就对了。

源码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
#define MAXN 105
#define MAXM 90005
#define MAXX 10000005
#define reg register
typedef long long LL;
typedef pair<int,LL> pii;
const int INF=0x7f7f7f7f;
const LL inf=0x7f7f7f7f7f7f;
template<typename _T>
inline void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while('0'>s||'9'<s){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
int n,m,head[MAXM],tot,pre[MAXM],pw[MAXM],cnt,idx;LL dis[MAXM];
int S,T,fl[MAXM],sum,p[MAXN],ck[MAXM],fd[MAXM],mk[MAXN][MAXN];
queue<int> q;bool vis[MAXM];
struct edge{int to,nxt,flow;LL paid;int op;}e[MAXX];
struct ming{int a,b,s,t;}s[MAXN];
inline void addEdge(const int u,const int v,const int f,const LL w){e[++tot]=(edge){v,head[u],f,w};head[u]=tot;}
inline void addedge(const int u,const int v,const int f,const LL w){addEdge(u,v,f,w);e[tot].op=tot+1;addEdge(v,u,0,-w);e[tot].op=tot-1;}
inline pii sakura(){
	int mf=0;LL res=0;
	while(1){
		while(!q.empty())q.pop();
		for(reg int i=1;i<=cnt;++i)dis[i]=inf,fl[i]=pre[i]=pw[i]=0,vis[i]=0;
		q.push(S);dis[S]=0;vis[S]=1;fl[S]=INF;
		while(!q.empty()){
			const int u=q.front();q.pop();vis[u]=0;if(!fl[u])continue;
			for(reg int i=head[u];i;i=e[i].nxt){
				const int v=e[i].to;
				if(dis[v]>dis[u]+1ll*e[i].paid&&e[i].flow>0){
					dis[v]=dis[u]+1ll*e[i].paid;pre[v]=u;pw[v]=i;
					if(!vis[v])q.push(v),vis[v]=1,fl[v]=min(fl[u],e[i].flow);
				}
			}
		}
		if(!pre[T])break;mf+=fl[T];res+=1ll*fl[T]*dis[T];int id=pre[T];
		for(reg int i=T;i^S;i=pre[i])e[pw[i]].flow-=fl[T],e[e[pw[i]].op].flow+=fl[T];
		addedge(++idx,T,1,0);ck[idx]=ck[id];fd[idx]=fd[id]+1;
		for(reg int i=1;i<=n;++i)addedge(i,idx,1,1ll*fd[idx]*mk[ck[idx]][i]);
	}
	return make_pair(mf,res);
}
signed main(){
	read(n);read(m);
	for(reg int i=1;i<=n;++i)read(p[i]),sum+=p[i];
	S=sum+m+n+1;cnt=T=sum+m+n+2;
	for(reg int i=1;i<=n;++i)addedge(S,i,p[i],0),++idx;
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)read(mk[j][i]);
	for(reg int i=1;i<=m;++i){
		addedge(++idx,T,1,0);ck[idx]=i;fd[idx]=1;
		for(reg int j=1;j<=n;++j)
			addedge(j,idx,1,1ll*mk[i][j]);
	}
	pii ans=sakura();printf("%lld\n",ans.second);
	return 0;
}

谢谢!!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值