题面

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;
}

祝大家学习愉快!

题解-CF802C Heidi and Library (hard)的更多相关文章

  1. CF802C Heidi and Library hard 费用流 区间k覆盖问题

    LINK:Heidi and Library 先说一下简单版本的 就是权值都为1. 一直无脑加书 然后发现会引起冲突,可以发现此时需要扔掉一本书. 扔掉的话 可以考虑扔掉哪一本是最优的 可以发现扔掉n ...

  2. CF802C Heidi and Library (hard)

    题目描述 你有一个容量为k的空书架,现在共有n个请求,每个请求给定一本书ai,如果你的书架里没有这本书,你就必须以ci的价格购买这本书放入书架.当然,你可以在任何时候丢掉书架里的某本书.请求出完成这n ...

  3. CF802C Heidi and Library (hard) 最小费用流

    你有一个容量为k的空书架,现在共有n个请求,每个请求给定一本书ai,如果你的书架里没有这本书,你就必须以ci的价格购买这本书放入书架. 当然,你可以在任何时候丢掉书架里的某本书.请求出完成这n个请求所 ...

  4. 【CF802C】Heidi and Library(网络流)

    [CF802C]Heidi and Library(网络流) 题面 CF 洛谷 题解 前面两个Easy和Medium都是什么鬼玩意啊.... 不难发现如果这天的要求就是第\(a_i\)种书的话,那么\ ...

  5. 【CF802C】Heidi and Library (hard) 费用流

    [CF802C]Heidi and Library (hard) 题意:有n个人依次来借书,第i人来的时候要求书店里必须有种类为ai的书,种类为i的书要花费ci块钱购入.而书店的容量只有k,多余的书只 ...

  6. C. Heidi and Library (神奇的网络流)

    C. Heidi and Library 题意 有 n 种分别具有价格 b 的书 a ,图书馆里最多同时存放 k 本书,已知接下来 n 天每天都有一个人来看某一本书,如果图书馆里没有则需要购买,问最少 ...

  7. 贪心算法 Heidi and Library (easy)

    A. Heidi and Library (easy) time limit per test 2 seconds memory limit per test 256 megabytes input ...

  8. 【CF802C】 Heidi and Library (hard)(费用流)

    题目链接 感觉跟餐巾计划问题有点像.费用流. 决定每天买不买不太好搞,不如先把所有东西都买进来,再卖掉不必要的. 拆点,每个点拆成\(x,y\). 源点向每个点的\(x\)连费用为当天的价格,流量为1 ...

  9. 【贪心】codeforces B. Heidi and Library (medium)

    http://codeforces.com/contest/802/problem/B [题意] 有一个图书馆,刚开始没有书,最多可容纳k本书:有n天,每天会有人借一本书,当天归还:如果图书馆有这个本 ...

随机推荐

  1. tcpack--4延时ack

    TCP在收到数据后必须发送ACK给对端,但如果每收到一个包就给一个ACK的话会使得网络中被注入过多报文.TCP的做法是在收到数据时不立即发送ACK,而是设置一个定时器,如果在定时器超时之前有数据发送给 ...

  2. Innodb之全局共享内存

    参考链接: https://blog.csdn.net/miyatang/article/details/54881547 https://blog.csdn.net/wyzxg/article/de ...

  3. 【鸿蒙应用开发】使用确切位置布局(PositionLayout)实现登录页面

    上一节我们了解了PositionLayout(确切位置布局,我更倾向于称为绝对布局),虽然应用场景稀少.维护不方便,但是该有的示例还是不能少. UI图拆解及代码实现 这个界面我们是不是很熟悉,打开浏览 ...

  4. SpringBean生命周期-Version-v5.1.0.RELEASE

    首先入口选定在org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons ...

  5. linux centos 6.x 装机后基本优化

    1.关闭SELinux /etc/selinux/config配置文件内替换 se -i 's/SELINUX=enforcing/SELINUX=disabled/g'需要重启grep SELINU ...

  6. Linux bash反弹shell原理引起的一点思考

        最近,一起做安全测试的兄弟问我:Linux反弹shell的命令是什么,我毫不犹豫地在笔记中找到发给了他,可是脑海中突然闪过有个疑问,为啥这样能反弹shell呢,用了那么多次却从来没有想过这个问 ...

  7. 还不懂spring中的bean的话,你一定得好好看看这篇文章

    bean的作用域 bean的生命周期 bean的装配 代码 实体类 package com; import java.util.List; public class User { private St ...

  8. cProfile分析程序性能

    Python标准库中提供了三种用来分析程序性能的模块,分别是cProfile, profile和hotshot,另外还有一个辅助模块stats.这些模块提供了对Python程序的确定性分析功能,同时也 ...

  9. Word 2013中如何直接调用MathType

    相信有很多用户已经发现在使用Word 2013编辑文档时MathType无法直接调用,但是点击文档中的公式时能够跳出MathType公式编辑窗口,那么这是怎么回事呢?其实,这一问题也不是没有办法解决的 ...

  10. guitar pro系列教程(十六):Guitar Pro如何编辑琵音

    上一章节我们讲了播放没有声音的解决,本章节我们通过图文结合的方式为大家讲解使用Guitar Pro如何来编辑琵音,有兴趣的朋友可以一起来学习哦. 首先我们要先搞明白什么事吉他的琵音. 其实吉他琶音就是 ...