题解-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天,每天会有人借一本书,当天归还:如果图书馆有这个本 ...
随机推荐
- kafka消费者offset存储策略
由于 consumer 在消费过程中可能会出现断电宕机等故障,consumer 恢复后,需要从故 障前的位置的继续消费,所以 consumer 需要实时记录自己消费到了哪个 offset,以便故障恢 ...
- APP分享多张图片到微信和朋友圈
产品需求: 微信分享多图至好友,朋友圈.由于微信禁用了分享9图至朋友圈功能,这里分享微信只是将图片保存至本地,具体让用户手动分享. 问题分析: 微信没有提供分享多图的SDK,因此我们实现调用系统自带的 ...
- centos使用U盘做启动盘
软件下载地址: http://sourceforge.net/projects/iso2usb/files/latest/download?source=dlp 写于: 2014年08月04日 更新于 ...
- git-关联远程git仓库详细步骤-2
1.打开git bash,在控制台中输入以下命令:ssh-keygen -t rsa -C "邮箱地址" 结果: fanxi@AT8350 MINGW64 ~$ ssh-keyge ...
- SecureCRT下载和安装
1.下载地址在我的百度网盘中 链接:https://pan.baidu.com/s/1tscAAS7QnWEQMNtnvGGI_A 提取码:exp9 2.解压后,运行 选中SecureCRT运行程序 ...
- 我要进大厂之大数据MapReduce知识点(2)
01 我们一起学大数据 今天老刘分享的是MapReduce知识点的第二部分,在第一部分中基本把MapReduce的工作流程讲述清楚了,现在就是对MapReduce零零散散的知识点进行总结,这次的内容大 ...
- vue项目中h5移动端中通过flex布局实现首尾固定,中间滚动(借鉴)
html中 <div class="flexLayoutr"> <div class="div_head"></div> & ...
- zabbix的搭建及操作(3)监控 MySQL 及 HTTP 状态监控
书接上回 -- 详情点击 Server端以配置好 mariadb(MySQL) 及 http 服务 Zabbix实现监控 mysql 数据库 server服务器端配置 vim /usr/local/z ...
- 怎么用MindManager制作议论文思维导图
大家都写过作文吧,做小学到高考到大学,这是谁也摆脱不了的,但是大家写作文会提前把自己的思路整理出来吗?让自己行文更为顺畅,作文更为流利吗?特别是关于议论文,一直是高考写作的一个重点篇目,写好议论文,就 ...
- 破解版的OCR文字识别软件,真的好用吗?
很多小伙伴在下载OCR文字识别软件时,会习惯性去找破解版的软件.那么到底什么是破解版的软件呢?其实破解的软件,都是通过非法的手段,破除正版软件的安全权限制作而成的.因此,使用这些破解软件会存在很多安全 ...