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();
}
posted @ 2024-11-17 15:10  liyixin  阅读(6)  评论(0编辑  收藏  举报