Loading

题解-CF802C Heidi and Library (hard)

题面

CF802C Heidi and Library (hard)

有一个大小为 \(k\) 的空书架。有 \(n\) 天和 \(n\) 种书,每天要求书架中有书 \(a_i\)。每天可以多次买书,买书 \(i\) 的价格为 \(c_i\)。每天可以扔书(因为书架满了)。求满足要求的最小代价。

数据范围:\(1\le n,k\le 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\neq 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<<'\n';
    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<<'\n';
    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<<'\n';
    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<<'\n';
    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\neq a_v)\) 表示换书,\((u_1,v_0,1,0)(u<v,a_u=a_v)\) 表示沿用书。

然后跑图,一旦某次 spfa 以后 \(dep_t\ge 0\) 了就停止(最小费用可行流),答案是费用 \(+n\cdot \infty\)

点数 \(\Theta(n)\),边数 \(\Theta(n^2)\)

这个做法有个神奇之处:可以通过不停增加 \(s\)\(mid\) 之间的流量并增广流量 \(1\),高效地求出 \(k=1\sim 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<<'\n';
    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<<'\n';
    return 0;
}

祝大家学习愉快!

posted @ 2020-11-20 15:12  George1123  阅读(119)  评论(0编辑  收藏  举报