CF802C Heidi and Library (hard) 最小费用流
你有一个容量为k的空书架,现在共有n个请求,每个请求给定一本书ai,如果你的书架里没有这本书,你就必须以ci的价格购买这本书放入书架。
当然,你可以在任何时候丢掉书架里的某本书。请求出完成这n个请求所需要的最少价钱。
做法1:
把每个请求拆成两个点 A,B
A表示买入 B表示卖出
addedge(S,A,1,c[a[i]]) addedge(A,B,1,0) addedge(B,T,1,0) 这样最大流必是n
每天的A向下一天的A连流量为k-1,费用为0的边,表示可以不扔,留到明天,但明天的书还需要一个位置,所以是k-1。
每天的前一天向上一个书出现的位置的B连一条费用为−c[a[i]],流量为1的边,表示在已经有这本书的情况下,可以卖掉这本书
跑一遍最小费用即为答案
#include <cstdio> #include <queue> #include <cstring> #define INF 2147483647 using namespace std; const int MAXN = 210; const int MAXM = 200010; queue <int> q; int s, t, now, n; struct Edge{ int from, next, to, rest, cost; }e[MAXM]; int head[MAXN], num = 1, dis[MAXN], vis[MAXN], Flow[MAXN], pre[MAXN]; inline void Add(int from, int to, int flow, int cost){ e[++num] = (Edge){ from, head[from], to, flow, cost }; head[from] = num; e[++num] = (Edge){ to, head[to], from, 0, -cost }; head[to] = num; } int RoadsExist(){ q.push(s); memset(dis, 127, sizeof dis); dis[s] = 0; Flow[s] = INF; pre[t] = 0; while(!q.empty()){ now = q.front(); q.pop(); vis[now] = 0; for(int i = head[now]; i; i = e[i].next) if(e[i].rest && dis[e[i].to] > dis[now] + e[i].cost){ dis[e[i].to] = dis[now] + e[i].cost; pre[e[i].to] = i; Flow[e[i].to] = min(Flow[now], e[i].rest); if(!vis[e[i].to]){ vis[e[i].to] = 1; q.push(e[i].to); } } } return pre[t]; } int k, maxflow, mincost, sum; int a[MAXN], c[MAXN], last[MAXN]; int main(){ scanf("%d%d", &n, &k); s = 199; t = 200; for(int i = 1; i <= n; ++i) scanf("%d", &a[i]); for(int i = 1; i <= n; ++i) scanf("%d", &c[i]); for(int i = 1; i <= n; ++i){ Add(s, i, 1, c[a[i]]); if(i != 1) Add(i - 1, i, k - 1, 0); Add(i, i + n, 1, 0); if(last[a[i]]) Add(i - 1, last[a[i]] + n, 1, -c[a[i]]); Add(i + n, t, 1, 0); last[a[i]] = i; } while(RoadsExist()){ mincost += Flow[t] * dis[t]; for(int i = t; i != s; i = e[pre[i]].from){ e[pre[i]].rest -= Flow[t]; e[pre[i] ^ 1].rest += Flow[t]; } } printf("%d\n", mincost); return 0; }
做法2:
把每个请求拆成两个点 A,B
A表示买入 B表示卖出
addedge(S,A,1,c[a[i]]) addedge(A,B,1,-INF) addedge(B,T,1,0) 这样最大流同样是n
然后n2遍历每个点对 如果书类型相同则addedge(Bi,Aj,1,0)表示这本书可以给第j个用
类型不同则addedge(Bi,Aj,1,c[a[j]]) 表示花这么多钱买入
然后跑min(n,k)次 SPFA 得到的费用即为答案(也可以拆S点来限制流量)
为什么要跑min(n,k)次的原因我认为是对于容量为min(n,k)的书架 其实你就是要选择min(n,k)个起始书 之后寻找最短路的最优方案即为寻找换书的最优方案
#include<iostream> #include<cstdio> #include<cstring> #include<queue> using namespace std; #define MAX 85 const int inf=1e7; inline int read() { int x=0;bool t=false;char ch=getchar(); while((ch<'0'||ch>'9')&&ch!='-')ch=getchar(); if(ch=='-')t=true,ch=getchar(); while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar(); return t?-x:x; } namespace MCMF { const int MAXM=1000000,MAXN=1000; struct Line{int v,next,w,fy;}e[MAXM]; int h[MAXN],cnt=2; inline void Add(int u,int v,int w,int fy) { e[cnt]=(Line){v,h[u],w,fy};h[u]=cnt++; e[cnt]=(Line){u,h[v],0,-fy};h[v]=cnt++; } int dis[MAXN],pe[MAXN],pv[MAXN],Cost,Flow; bool vis[MAXN];queue<int> Que; int S=MAXN-2,T=MAXN-1; bool SPFA() { memset(dis,63,sizeof(dis));dis[S]=0; Que.push(S);vis[S]=true; while(!Que.empty()) { int u=Que.front();Que.pop(); for(int i=h[u];i;i=e[i].next) { int v=e[i].v;if(!e[i].w)continue; if(dis[u]+e[i].fy<dis[v]) { dis[v]=dis[u]+e[i].fy;pe[v]=i,pv[v]=u; if(!vis[v])vis[v]=true,Que.push(v); } } vis[u]=false; } if(dis[T]>=1e9)return false; int flow=1e9; for(int i=T;i!=S;i=pv[i])flow=min(flow,e[pe[i]].w); for(int i=T;i!=S;i=pv[i])e[pe[i]].w-=flow,e[pe[i]^1].w+=flow; Flow+=flow;Cost=min(Cost,Cost+dis[T]*flow); return true; } } using namespace MCMF; int n,K,a[MAX],c[MAX]; int main() { n=read();K=read(); for(int i=1;i<=n;++i)a[i]=read(); for(int i=1;i<=n;++i)c[i]=read(); for(int i=1;i<=n;++i)Add(S,i,1,c[a[i]]); for(int i=1;i<=n;++i)Add(i,i+n,1,-inf); for(int i=1;i<=n;++i)Add(i+n,T,1,0); for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j) Add(i+n,j,1,a[i]==a[j]?0:c[a[j]]); Cost+=n*inf;K=min(K,n);while(K--)SPFA(); printf("%d\n",Cost); return 0; }