题面
有一个大小为 (k) 的空书架。有 (n) 天和 (n) 种书,每天要求书架中有书 (a_i)。每天可以多次买书,买书 (i) 的价格为 (c_i)。每天可以扔书(因为书架满了)。求满足要求的最小代价。
数据范围:(1le n,kle 80)。
路标
很明显这是一道套路烂大街的水题,但是今天蒟蒻想讲 (3) 种巧妙的做法。
此题的输出方案版:CF132E Bits of merry old England
((u,v,f,c)) 表示连一条 (u) 到 (v) 容量 (f) 费用 (c) 的边,并建它的反悔边。
题解 1
这个蒟蒻的做法,是当前最劣解。
根据时间和书种拆点 ((time,book))。
增加一种书种叫“空”((n)),增加一个时间为开始时((0))。
先 ((s,(0,n),k,0)),流量 (x) 流到点 ((i,j)) 表示在第 (i) 时间 (x) 这个书架位置是书 (j)。
(((i,j),(i+1,x)(x eq j),1,c_x)),表示这个位置换书。很明显一天换一本书足矣。
(((i,j),(i+1,j),k,0)),表示不换书。
如果 (j=a_i),把 ((i,j)) 拆成 ((i,j)_0) 和 ((i,j)_1),(((i,j)_0,t,1,0)),((s,(i,j)_1,1,0)),表示要求有 (j) 这本书。(((i,j)_0,(i,j)_1,k-1,0))。
(((n,j),mid,k,0)),((mid,t,k,0))。(mid) 的作用是限制流量。
然后跑图,流量必定是 (n+k) 费用是答案。
点数 (Theta(n^2)),边数 (Theta(n^3))。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define x first
#define y second
#define bg begin()
#define ed end()
#define pb push_back
#define mp make_pair
#define sz(a) int((a).size())
#define R(i,n) for(int i(0);i<(n);++i)
#define L(i,n) for(int i((n)-1);~i;--i)
const int iinf=0x3f3f3f3f;
const ll linf=0x3f3f3f3f3f3f3f3f;
//Data
const int N=80;
int n,k,a[N],c[N];
//Flows
const int fN=(N+1)*(N+1)+N+3;
int fn,s,t,mid,dep[fN],pre[fN],q[fN],*ta,*he;
bool vis[fN];
vector<int> e[fN],to,fw,co;
void adde(int u,int v,int w,int c){
// cout<<u<<' '<<v<<' '<<w<<'-'<<c<<'
';
e[u].pb(sz(to)),to.pb(v),fw.pb(w),co.pb(+c);
e[v].pb(sz(to)),to.pb(u),fw.pb(0),co.pb(-c);
}
bool spfa(){
R(u,fn) dep[u]=iinf,vis[u]=false,pre[u]=-1;
ta=he=q,dep[*ta++=pre[s]=s]=0,vis[s]=true;
while(ta!=he){
int u=*he++; he-q>=fn&&(he-=fn),vis[u]=false;
for(int v:e[u])if(fw[v]&&dep[to[v]]>dep[u]+co[v])
dep[to[v]]=dep[u]+co[v],pre[to[v]]=v,
!vis[to[v]]&&(*ta++=to[v],ta-q>=fn&&(ta-=fn),vis[to[v]]=true);
}
return dep[t]^iinf;
}
pair<int,int> flow(){
pair<int,int> res(0,0);
while(spfa()){
int f=iinf;
for(int u=t;u^s;u=to[pre[u]^1]) f=min(f,fw[pre[u]]);
for(int u=t;u^s;u=to[pre[u]^1]) fw[pre[u]]-=f,fw[pre[u]^1]+=f;
res.x+=f,res.y+=dep[t]*f;
}
return res;
}
//Main
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>k,fn=(t=(s=(mid=(n+1)*(n+1)+n)+1)+1)+1;
#define p(i,j) ((i)*(n+1)+(j))
R(i,n) cin>>a[i],--a[i]; R(i,n) cin>>c[i];
adde(mid,t,k,0),adde(s,p(0,n),k,0);
R(i,n+1) adde(i==a[n-1]?(n+1)*(n+1)+n-1:p(n,i),mid,k,0);
R(i,n){
adde(p(i+1,a[i]),t,1,0),adde(s,(n+1)*(n+1)+i,1,0);
adde(p(i+1,a[i]),(n+1)*(n+1)+i,k-1,0);
R(j,n+1){
int last=(i&&j==a[i-1])?(n+1)*(n+1)+i-1:p(i,j);
adde(last,p(i+1,j),k,0);
R(t,n) (j^t)&&(adde(last,p(i+1,t),1,c[t]),true);
}
}
pair<int,int> ns(flow());
assert(ns.x==k+n),cout<<ns.y<<'
';
return 0;
}
题解 2
神仙 @mrsrz
的做法,非常巧妙。
每本需要书可以在当天先买上,如果手上已经有这本书了可以把手上这本卖了。
把每天拆成 (u_0) 和 (u_1),((s,u_0,1,c_{a_u})) 表示买书,((u_0,u_1,1,0)) 表示供书,((u_1,t,1,0)) 表示提交书,((u_0,(u+1)_0,k-1,0)) 表示存书((k-1) 是因为留给下一天的书位置),(((u-1)_0,p(a_u)_1,1,-c_{a_u})) 表示卖书((p(x)) 表示第 (x) 种书上次出现位置)。
点数 (Theta(n)),边数 (Theta(n))。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define x first
#define y second
#define bg begin()
#define ed end()
#define pb push_back
#define mp make_pair
#define sz(a) int((a).size())
#define R(i,n) for(int i(0);i<(n);++i)
#define L(i,n) for(int i((n)-1);~i;--i)
const int iinf=0x3f3f3f3f;
const ll linf=0x3f3f3f3f3f3f3f3f;
//Data
const int N=80;
int n,k,a[N],c[N],p[N];
//Flows
const int fN=(N<<1)+2;
int fn,s,t,dep[fN],pre[fN],q[fN],*ta,*he;
bool vis[fN];
vector<int> e[fN],to,fw,co;
void adde(int u,int v,int w,int c){
// cout<<u<<' '<<v<<' '<<w<<'-'<<c<<'
';
e[u].pb(sz(to)),to.pb(v),fw.pb(w),co.pb(+c);
e[v].pb(sz(to)),to.pb(u),fw.pb(0),co.pb(-c);
}
bool spfa(){
R(u,fn) dep[u]=iinf,vis[u]=false,pre[u]=-1;
ta=he=q,dep[*ta++=pre[s]=s]=0,vis[s]=true;
while(ta!=he){
int u=*he++; he-q>=fn&&(he-=fn),vis[u]=false;
for(int v:e[u])if(fw[v]&&dep[to[v]]>dep[u]+co[v])
dep[to[v]]=dep[u]+co[v],pre[to[v]]=v,
!vis[to[v]]&&(*ta++=to[v],ta-q>=fn&&(ta-=fn),vis[to[v]]=true);
}
return dep[t]^iinf;
}
pair<int,int> flow(){
pair<int,int> res(0,0);
while(spfa()){
int f=iinf;
for(int u=t;u^s;u=to[pre[u]^1]) f=min(f,fw[pre[u]]);
for(int u=t;u^s;u=to[pre[u]^1]) fw[pre[u]]-=f,fw[pre[u]^1]+=f;
res.x+=f,res.y+=dep[t]*f;
}
return res;
}
//Main
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>k,fn=(t=(s=n<<1)+1)+1;
R(i,n) cin>>a[i],--a[i];
R(i,n) cin>>c[i],p[i]=-1;
R(i,n){
adde(i,i+n,1,0),adde(i+n,t,1,0);
adde(s,i,1,c[a[i]]),i+1<n&&(adde(i,i+1,k-1,0),true);
~p[a[i]]&&(adde(i-1,p[a[i]]+n,1,-c[a[i]]),true),p[a[i]]=i;
}
cout<<flow().y<<'
';
return 0;
}
题解 3
根据 @Um_nik
和 @Kronecker
写的当前 Codeforces
上此题最优解改编,到这里看 原版。
原理是反悔贪心,用网络流来实现。
同样把点拆成 (u_0) 和 (u_1),((u_0,u_1,1,-infty)) 表示每次增广后都必须选此边,((s,mid,k,0)) 限制流量,((mid,u_0,1,c_{a_u})) 表示买书,((u_1,t,1,0)) 表示交书,((u_1,v_0,1,c_{a_v})(u<v,a_u eq a_v)) 表示换书,((u_1,v_0,1,0)(u<v,a_u=a_v)) 表示沿用书。
然后跑图,一旦某次 spfa
以后 (dep_tge 0) 了就停止(最小费用可行流),答案是费用 (+ncdot infty)。
点数 (Theta(n)),边数 (Theta(n^2))。
这个做法有个神奇之处:可以通过不停增加 (s) 和 (mid) 之间的流量并增广流量 (1),高效地求出 (k=1sim K) 时的答案,只不过需要把上面那句话的停止变成连一条 ((s,t,infty,0)) 的边。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define x first
#define y second
#define bg begin()
#define ed end()
#define pb push_back
#define mp make_pair
#define sz(a) int((a).size())
#define R(i,n) for(int i(0);i<(n);++i)
#define L(i,n) for(int i((n)-1);~i;--i)
const int iinf=0x3f3f3f3f;
const ll linf=0x3f3f3f3f3f3f3f3f;
//Data
const int N=80,F=3e6;
int n,k,mid,a[N],c[N],p[N];
//Flows
const int fN=(N<<1)+3;
int fn,s,t,dep[fN],pre[fN],q[fN],*ta,*he;
bool vis[fN];
vector<int> e[fN],to,fw,co;
void adde(int u,int v,int w,int c){
// cout<<u<<' '<<v<<' '<<w<<'-'<<c<<'
';
e[u].pb(sz(to)),to.pb(v),fw.pb(w),co.pb(+c);
e[v].pb(sz(to)),to.pb(u),fw.pb(0),co.pb(-c);
}
bool spfa(){
R(u,fn) dep[u]=iinf,vis[u]=false,pre[u]=-1;
ta=he=q,dep[*ta++=pre[s]=s]=0,vis[s]=true;
while(ta!=he){
int u=*he++; he-q>=fn&&(he-=fn),vis[u]=false;
for(int v:e[u])if(fw[v]&&dep[to[v]]>dep[u]+co[v])
dep[to[v]]=dep[u]+co[v],pre[to[v]]=v,
!vis[to[v]]&&(*ta++=to[v],ta-q>=fn&&(ta-=fn),vis[to[v]]=true);
}
return dep[t]^iinf;
}
pair<int,int> flow(){
pair<int,int> res(0,0);
while(spfa()){
if(dep[t]>=0) break;
int f=iinf;
for(int u=t;u^s;u=to[pre[u]^1]) f=min(f,fw[pre[u]]);
for(int u=t;u^s;u=to[pre[u]^1]) fw[pre[u]]-=f,fw[pre[u]^1]+=f;
res.x+=f,res.y+=dep[t]*f;
}
return res;
}
//Main
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>k,fn=(t=(s=(mid=n<<1)+1)+1)+1,adde(s,mid,k,0);
R(i,n) cin>>a[i],--a[i]; R(i,n) cin>>c[i];
R(i,n) adde(mid,i,1,c[a[i]]),adde(i,i+n,1,-F),adde(i+n,t,1,0);
R(j,n)R(i,j) adde(i+n,j,1,a[i]==a[j]?0:c[a[j]]);
cout<<flow().y+F*n<<'
';
return 0;
}
祝大家学习愉快!