zoukankan      html  css  js  c++  java
  • 题解-CF802C Heidi and Library (hard)

    题面

    CF802C Heidi and Library (hard)

    有一个大小为 (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;
    }
    

    祝大家学习愉快!

  • 相关阅读:
    20201231《信息安全专业导论》第一周学习总结
    Python模拟进程状态
    Python gui
    2020-2021-1 20201208《信息安全专业导论》第10周学习总结
    2020-2021-1 20201208 《信息安全专业导论》第九周学习总结
    熟悉编程语言
    俄罗斯方块
    小学四则运算编程实践
    2020-2021-1-1 《信息安全专业导论》第八周学习总结
    2020-2021-1 20201208 《信息安全专业导论》第七周学习总结
  • 原文地址:https://www.cnblogs.com/George1123/p/14011031.html
Copyright © 2011-2022 走看看