CF802C Heidi and Library (hard)
CF802C Heidi and Library (hard)
模拟退火爆切流题
题意
cplusoj:SS241116A. 书架(book)
NOIP 模拟赛 T1 网络流真的合适吗
有一个容量为 \(m\) 的书架,有 \(n\) 种书,每种书有一个价格 \(c_i\),有 \(n\) 次操作,每次操作给出一种书 \(x\),如果你的书架上面没有这种书,你就必须花费 \(c_x\) 的价格买下它,否则就不用买。要求任意时刻书架上不超过 \(m\) 本书。为最小花费。
思路
最初的方案是每买一本书就马上扔掉,花费 \(\sum c_{a_i}\)。然后问题转化成保留一些书,最大化价值。
考虑 \(i\) 不需要买的情况,当且仅当前一个和它颜色相同的书没有被扔掉,设前一个颜色相同的是 \(la_{a_i}\),那么在时间 \([la_{a_i},i)\) 中,第 \(la_{a_i}\) 本书会一直在书架上面。
因此问题就是有 \(O(n)\) 个区间,最大化选择的区间价值,使得每个时间被覆盖的次数不超过 \(m-1\)(不是 \(m\),因为每个时间你需要一个空的位置来买书以及马上扔掉)。
这个非常的网络流,但是我不会建模。。。
模拟退火
于是可以模拟退火。给所有区间钦定一个选择顺序,然后按顺序贪心地选择,如果能选就直接选上。(一次贪心可以使用线段树优化,\(O(n \log n)\))显然这个贪心是错误的,所以每次退火随机交换两个顺序,卡个时间一直做退火。
CF 上面居然可以过。
code
// sa yyds!
#include<bits/stdc++.h>
#define sf scanf
#define pf printf
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
using namespace std;
typedef long long ll;
namespace plastic {
constexpr int N=85;
int n,a[N],c[N],m;
ll ans;
struct edge {
int l,r,w;
};
vector<edge> vec;
int la[N];
bool del[N];
int s;
constexpr double time_limit=0.95,T=10,delta=0.99,eps=1e-15;
mt19937 rd(random_device{}());
ll mx;
struct seg {
int tr[N<<2],tag[N<<2];
void clear() { memset(tr,0,sizeof(tr)), memset(tag,0,sizeof(tag)); }
void maketag(int u,int t) {
tr[u]+=t,tag[u]+=t;
}
void pushup(int u) { tr[u]=max(tr[u<<1],tr[u<<1|1]); }
void pushdown(int u) {
if(tag[u]) maketag(u<<1,tag[u]),maketag(u<<1|1,tag[u]), tag[u]=0;
}
void insert(int u,int l,int r,int L,int R) {
if(l>=L&&r<=R) return maketag(u,1), void(0);
int mid=(l+r)>>1;
pushdown(u);
if(L<=mid) insert(u<<1,l,mid,L,R);
if(mid+1<=R) insert(u<<1|1,mid+1,r,L,R);
pushup(u);
}
int query(int u,int l,int r,int L,int R) {
if(l>=L&&r<=R) return tr[u];
int mid=(l+r)>>1;
pushdown(u);
int s=0;
if(L<=mid) s=query(u<<1,l,mid,L,R);
if(mid+1<=R) s=max(s,query(u<<1|1,mid+1,r,L,R));
pushup(u);
return s;
}
}Tr;
ll calc() {
ll sum=0;
Tr.clear();
for(auto u : vec) {
if(u.l+1<=u.r-1) {
if(Tr.query(1,1,n,u.l+1,u.r-1)<=m-2) sum+=u.w, Tr.insert(1,1,n,u.l+1,u.r-1);
} else sum+=u.w;
}
return sum;
}
void sa() {
double t=T;
while(t>eps) {
int x=rd()%s,y=rd()%s;
swap(vec[x],vec[y]);
ll now=calc();
if(now > mx) mx=now;
else if(1.0*(mx-now)*T>rand()/RAND_MAX) swap(vec[x],vec[y]);
t*=delta;
}
}
void main() {
clock_t st=clock();
srand(time(0));
sf("%d%d",&n,&m);
rep(i,1,n) sf("%d",&a[i]);
rep(i,1,n) sf("%d",&c[i]);
rep(i,1,n) {
ans+=c[a[i]];
if(la[a[i]]) vec.push_back({la[a[i]],i,c[a[i]]});
la[a[i]]=i;
}
s=vec.size();
while(clock()-st<time_limit*CLOCKS_PER_SEC) sa();
pf("%lld\n",ans-mx);
}
}
int main() {
plastic :: main();
}
网络流
来自 https://www.luogu.com.cn/discuss/995933 提供的做法。
建 \(n\) 个点,\(i\) 向 \(i+1\) 连容量为 \(m-1\),费用为 \(0\) 的边,代表你不选择区间的时候没有费用。每个区间 \([l,r,v]\) 从 \(l\) 往 \(r\) 连一条容量为 \(1\),费用为 \(v\) 的边,代表使用一个容量可以获得 \(v\) 的费用。然后跑一个最大费用最大流,其实流量一定是 \(m-1\)。我们可爱的 SPFA 是可以跑负权图的,把边权取负然后跑最小费用最大流就好了。时间复杂度是 \(O(nmf)\) 即 \(O(n^2m)\) 的。
code
注意对于上一个书和这一个书的情况会出现负权自环边,这种边判掉,直接必选就可以了。
唉,过了,跑的跟 std 一样快。
好写的。
#include<bits/stdc++.h>
#define sf scanf
#define pf printf
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
using namespace std;
typedef long long ll;
namespace plastic {
constexpr int N=85;
int n,a[N],c[N],m;
struct edge {
int to,f,ne,c;
}e[N<<8];
int cnt=1,head[N];
void addedge(int u,int v,int f,int w) {
e[++cnt]={v,f,head[u],w}, head[u]=cnt;
e[++cnt]={u,0,head[v],-w}, head[v]=cnt;
}
int la[N];
ll cost;
constexpr ll inf=0x3f3f3f3f3f3f3f3f;
ll dis[N];
int cur[N];
bool vi[N];
bool spfa() {
queue<int> q;
memset(dis,0x3f,sizeof(dis));
memcpy(cur,head,sizeof(cur));
vi[1]=1;
q.push(1);
dis[1]=0;
while(!q.empty()) {
int u=q.front();
q.pop();
vi[u]=0;
for(int i=head[u];i;i=e[i].ne) {
int v=e[i].to,c=e[i].c;
if(e[i].f && dis[u]+c<dis[v]) {
dis[v]=dis[u]+c;
if(!vi[v]) vi[v]=1, q.push(v);
}
}
}
return dis[n]!=inf;
}
int dinic(int u,int flow) {
if(u==n) return flow;
vi[u]=1;
int sum=0;
for(int &i=cur[u];i&&flow;i=e[i].ne) {
int v=e[i].to,c=e[i].c,f=e[i].f;
if(!vi[v] && f && dis[u]+c==dis[v]) {
int x=dinic(v,min(flow,f));
if(x) flow-=x, sum+=x, e[i].f-=x, e[i^1].f+=x, cost+=c*x;
else dis[v]=-1;
}
}
vi[u]=0;
return sum;
}
void mcmf() {
while(spfa()) dinic(1,m-1);
}
void main() {
sf("%d%d",&n,&m);
rep(i,1,n) sf("%d",&a[i]);
rep(i,1,n) sf("%d",&c[i]);
rep(i,1,n-1) addedge(i,i+1,m-1,0);
rep(i,1,n) {
cost+=c[a[i]];
if(la[a[i]]) {
if(la[a[i]]==i-1) cost-=c[a[i]];
else addedge(la[a[i]],i-1,1,-c[a[i]]);
}
la[a[i]]=i;
}
mcmf();
pf("%lld\n",cost);
}
}
int main() {
#ifdef LOCAL
freopen("my.out","w",stdout);
#else
freopen("book.in","r",stdin);
freopen("book.out","w",stdout);
#endif
plastic :: main();
}
本文来自博客园,作者:liyixin,转载请注明原文链接:https://www.cnblogs.com/liyixin0514/p/18550577