题面

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. 说一说packet poll 错误掩码的一个bug tcp udp packet poll细节有所不同 处理时需要注意

    今天处理一个cpu标高的bug,原因:在poll 返回后将error事件当做POLLIN事件处理,fd 一直都在唤醒线程处理,但是rcv的时候没有数据: unsigned int datagram_p ...

  2. windows10 vs2017编译opencv_contrib3.4.7的小坑及编译好的资源

    1.注意要用正斜杠  /    不要用 \  https://github.com/opencv/opencv/issues/11655 CMake Error at cmake/OpenCVModu ...

  3. 给你一条sql语句如何进行优化

    我们sql语句的书写是根据业务逻辑进行书写的,如果执行比较慢,那么我们对sql重写: 如分步查询,然后在代码层进行拼接:用临时表:改变sql语句的写法等等.我们称之为逻辑层优化. 然后我们看看每条sq ...

  4. Ceph 状态报警告 pool rbd has many more objects per pg than average (too few pgs?)

    定位问题 [root@lab8106 ~]# ceph -s cluster fa7ec1a1-662a-4ba3-b478-7cb570482b62 health HEALTH_WARN pool ...

  5. //*[starts-with(@class,'btn')][text()='差'] 正则定位元素

    starts-with?  //*[starts-with(@class,'btn')][text()='差']   意思找从头开始的这个class

  6. PID算法的C语言实现

    1.根据我控制算法类文章中关于PID的理论的一些描述,同时也根据网络上一些其他的PID文章,以及自己最近一个项目的实践后,总结了几套基于C语言的PID算法,由于网络中很少有人进行分享完整的PID算法实 ...

  7. Spring扩展之二:ApplicationListener

    1.介绍 用于监听应用程序事件的接口. 子接口:GenericApplicationListener,SmartApplicationListener. 通过ApplicationEvent类和App ...

  8. Win10系统下安装VC6.0教程

    学习一门语言最重要的一步是搭建环境,许多人搭建在搭建环境上撞墙了,就有些放弃的心理了:俗话说,工欲善其事,必先利其器:所以接下来我们进行学习C的第一步下载编程所用的工具;当然也有其它的软件,只不过初学 ...

  9. OWASP固件安全性测试指南

    OWASP固件安全性测试指南 固件安全评估,英文名称 firmware security testing methodology 简称 FSTM.该指导方法主要是为了安全研究人员.软件开发人员.顾问. ...

  10. SpringSecurity之授权

    SpringSecurity之授权 目录 SpringSecurity之授权 1. 写在前面的话 2. web授权 1. 建库 2. 添加查询权限的接口 3. 前端页面的编写 4. SpringSec ...