题解-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)的更多相关文章
- CF802C Heidi and Library hard 费用流 区间k覆盖问题
LINK:Heidi and Library 先说一下简单版本的 就是权值都为1. 一直无脑加书 然后发现会引起冲突,可以发现此时需要扔掉一本书. 扔掉的话 可以考虑扔掉哪一本是最优的 可以发现扔掉n ...
- CF802C Heidi and Library (hard)
题目描述 你有一个容量为k的空书架,现在共有n个请求,每个请求给定一本书ai,如果你的书架里没有这本书,你就必须以ci的价格购买这本书放入书架.当然,你可以在任何时候丢掉书架里的某本书.请求出完成这n ...
- CF802C Heidi and Library (hard) 最小费用流
你有一个容量为k的空书架,现在共有n个请求,每个请求给定一本书ai,如果你的书架里没有这本书,你就必须以ci的价格购买这本书放入书架. 当然,你可以在任何时候丢掉书架里的某本书.请求出完成这n个请求所 ...
- 【CF802C】Heidi and Library(网络流)
[CF802C]Heidi and Library(网络流) 题面 CF 洛谷 题解 前面两个Easy和Medium都是什么鬼玩意啊.... 不难发现如果这天的要求就是第\(a_i\)种书的话,那么\ ...
- 【CF802C】Heidi and Library (hard) 费用流
[CF802C]Heidi and Library (hard) 题意:有n个人依次来借书,第i人来的时候要求书店里必须有种类为ai的书,种类为i的书要花费ci块钱购入.而书店的容量只有k,多余的书只 ...
- C. Heidi and Library (神奇的网络流)
C. Heidi and Library 题意 有 n 种分别具有价格 b 的书 a ,图书馆里最多同时存放 k 本书,已知接下来 n 天每天都有一个人来看某一本书,如果图书馆里没有则需要购买,问最少 ...
- 贪心算法 Heidi and Library (easy)
A. Heidi and Library (easy) time limit per test 2 seconds memory limit per test 256 megabytes input ...
- 【CF802C】 Heidi and Library (hard)(费用流)
题目链接 感觉跟餐巾计划问题有点像.费用流. 决定每天买不买不太好搞,不如先把所有东西都买进来,再卖掉不必要的. 拆点,每个点拆成\(x,y\). 源点向每个点的\(x\)连费用为当天的价格,流量为1 ...
- 【贪心】codeforces B. Heidi and Library (medium)
http://codeforces.com/contest/802/problem/B [题意] 有一个图书馆,刚开始没有书,最多可容纳k本书:有n天,每天会有人借一本书,当天归还:如果图书馆有这个本 ...
随机推荐
- 第一章epoll
IO多路复用之epoll总结 1.基本知识 epoll是在2.6内核中提出的,是之前的select和poll的增强版本.相对于select和poll来说,epoll更加灵活,没有描述符限制.epoll ...
- 二、多线程及服务器编程总结------linux多线程服务端编程
- 差分进化算法介绍及matlab实现
引言 差分进化算法是基于群体智能理论的优化算法,是通过群体内个体间的合作与竞争而产生的智能优化搜索算法,它保留了基于种群的全局搜索策略,采用实数编码.基于差分的简单变异操作和"一对一&quo ...
- IDEA 2019.3.3 + Pycharm 2020.2.1 安装包及破解步骤
IDEA IDEA的破解流程就不用再说了,免费试用,添加VMOptions参数,选择破解jar的路径,重启IDEA. 下载地址:链接:https://pan.baidu.com/s/1aTRATVTL ...
- sql bypass waf fuzz python
从freebuf copy过来的,先保存,有空再改 #encoding=utf-8 import requests url = "http://127.0.0.1/index.php?id= ...
- VulnHub靶场学习_HA:Forensics
HA:Forensics Vulnhub靶场 下载地址:https://www.vulnhub.com/entry/ha-forensics,570/ 背景: HA: Forensics is an ...
- 上周我面了个三年 Javaer,这几个问题都没答出来
身为 Java Web 开发我发现很多人一些 Web 基础问题都答不上来. 上周我面试了一个三年经验的小伙子,一开始我问他 HTTP/1.HTTP/2相关的他到是能答点东西出来. 后来我问他:你知道 ...
- 面试官:小伙子,你能给我说一下HashMap的实现原理吗?
1. HashMap概述: HashMap是基于哈希表的Map接口的非同步实现(他与Hashtable类似,但Hashtable是线程安全的,所以是同步的实现),此实现提供可选的映射操作,允许使用nu ...
- 打开WPS时出现MathType错误弹窗怎么办
MathType是一款特别优秀的公式编辑器,特别是在文档中出现大量的复杂数学公式需要编辑时.不过MathType与Office的兼容性还算好,与WPS的兼容性要略逊一筹,有时候会出现如下的报错弹窗.提 ...
- 关于Vegas制作黑白负片爆闪效果的教程分享
作为一款视频剪辑软件,Vegas界面简洁,操作难度低,比较容易上手,今天小编就带大家了解Vegas制作超级炫酷的黑白负片爆闪效果的操作过程. 1.导入视频 首先,双击打开视频剪辑软件Vegas Pro ...