【NOI模拟赛】背包(优先队列,贪心)

前言

花田给点力啊别把老五人写崩了!/kk/kk/kk

题面


题解

这种题好像可以单独分一类:输出前 K K K 小方案(的权值)

其大致思路都是通过找到一种紧邻的调整方法,保证后继状态一定比前驱状态大,方案不重复(大概),并且转移的时空复杂度合理。这样便可以用优先队列实时取出下一个状态。

这一题是两个套起来的此类问题。

首先考虑 m = 1 m=1 m=1 的情况,只有一种颜色。若 l = r l=r l=r(即背包大小确定),我们把序列 a i a_i ai 排序,那么任意一种方案 { b 1 , b 2 , . . . , b l } ⊆ { 1 , 2 , 3 , . . . , n } \{b_1,b_2,...,b_l\}\sube\{1,2,3,...,n\} {b1,b2,...,bl}{1,2,3,...,n} 都可以通过如下转移唯一得到:

  • x x x 为满足 b x > b x − 1 + 1 b_x>b_{x-1}+1 bx>bx1+1 (或 x = 1 , b 1 > 1 x=1,b_1>1 x=1,b1>1)的最小值,那么把 { b 1 , b 2 , . . . , b x − 1 , . . . , b l } \{b_1,b_2,...,b_x-1,...,b_l\} {b1,b2,...,bx1,...,bl} 定为前驱状态,从 { 1 , 2 , . . . , l } \{1,2,...,l\} {1,2,...,l} 一直转移过来。

这样一来,每个状态的后继就只有 2 种: { b 1 , b 2 , . . . , b x + 1 + 1 , . . . , b l } \{b_1,b_2,...,b_{x+1}+1,...,b_l\} {b1,b2,...,bx+1+1,...,bl} { b 1 , b 2 , . . . , b x + 1 , . . . , b l } \{b_1,b_2,...,b_x+1,...,b_l\} {b1,b2,...,bx+1,...,bl} (前提是保证后继合法也就是 { b } \{b\} {b} 递增)。而且,一个状态有用的信息就只有:总和、 x x x b x + 1 b_{x+1} bx+1 b x + 2 b_{x+2} bx+2 。打包起来放优先队列里维护就好。

然后考虑 m > 1 m>1 m>1 的情况。现在我们已经可以得到每一个背包的前 K K K 小了,怎么利用这些前 K K K 小的信息来组装全局前 K K K 小呢?

这又是一个经典方法了:我们将背包按照 次小方案 − 最小方案 次小方案 - 最小方案 次小方案最小方案 的关键字从小到大排序,用序列 { c 1 , c 2 , . . . , c m } \{c_1,c_2,...,c_m\} {c1,c2,...,cm} 表示总方案( c i c_i ci 表示背包 i i i 选择了第 c i c_i ci 小的方案),那么有如下转移方案:

  • x x x 为满足 c x > 1 c_x>1 cx>1 的最大值,则前驱状态为 { c 1 , c 2 , . . . , max ⁡ ( c x − 1 , 1 + [ c x = 2 ] ) , c x − 1 , . . . , c m } \{c_1,c_2,...,\max(c_{x-1},1+[c_x=2]),c_x-1,...,c_m\} {c1,c2,...,max(cx1,1+[cx=2]),cx1,...,cm} (即 c x c_x cx 减少 1,若减到 1 了并且前一个位置为 1 ,则将前一个位置加一)。初始状态为 { 1 , 1 , . . . , c m = 1 } \{1,1,...,c_m=1\} {1,1,...,cm=1}

由于我们的排序,前驱状态一定比该状态更小。每个状态的后继最多 3 种: { c 1 , c 2 , . . . , c x + 1 , . . . , c m } \{c_1,c_2,...,c_x+1,...,c_m\} {c1,c2,...,cx+1,...,cm} { c 1 , c 2 , . . . , c x , 2 , . . . , c m } \{c_1,c_2,...,c_x,2,...,c_m\} {c1,c2,...,cx,2,...,cm} { c 1 , c 2 , . . . , c x − 1 , 2 , . . . , c m } ( c x = 2 ) \{c_1,c_2,...,c_x-1,2,...,c_m\}(c_x=2) {c1,c2,...,cx1,2,...,cm}(cx=2) 。有用的信息只有三个:总和、 x x x c x c_x cx 。还是打包起来放优先队列。

时间复杂度 O ( ( n + m + k ) log ⁡ ( n + m + k ) ) O((n+m+k)\log(n+m+k)) O((n+m+k)log(n+m+k))

CODE

#include<map>
#include<set>
#include<queue>
#include<vector>
#include<random>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#pragma GCC optimize(2)
using namespace std;
#define MAXN 200005
#define LL long long
#define ULL unsigned LL
#define DB double
#define lowbit(x) (-(x)&(x))
#define ENDL putchar('\n')
#define FI first
#define SE second
#define PR pair<int,int>
inline int xchar() {
	static const int maxn = 1000000;
	static char b[maxn];
	static int pos = 0,len = 0;
	if(pos == len) pos=0,len=fread(b,1,maxn,stdin);
	if(pos == len) return -1;
	return b[pos ++];
}
#define getchar() xchar()
inline LL read() {
	LL f=1,x=0;int s=getchar();
	while(s<'0'||s>'9') {if(s<0)return -1;if(s=='-')f=-f;s=getchar();}
	while(s>='0'&&s<='9') {x = (x<<3)+(x<<1)+(s^48);s=getchar();}
	return f*x;
}
void putpos(LL x) {if(!x)return ;putpos(x/10);putchar((x%10)^48);}
void putnum(LL x) {
	if(!x) {putchar('0');return ;}
	if(x<0) putchar('-'),x=-x;
	return putpos(x);
}
void AIput(LL x,int c) {putnum(x);putchar(c);}

int n,m,s,o,k;
vector<int> v[MAXN];
vector<int> b[MAXN];
vector<LL> a[MAXN];
#define prr pair<LL,pair<int,PR>>
#define prr2 pair<LL,PR>
priority_queue<prr,vector<prr>,greater<prr>> q[MAXN];
priority_queue<prr2,vector<prr2>,greater<prr2>> P;
void nexp(int i) {
	if(q[i].empty()) return ;
	auto t = q[i].top(); q[i].pop();
	int x = t.SE.FI,l = t.SE.SE.FI,r = t.SE.SE.SE;
	if(l < r-1) q[i].push({t.FI + v[i][l+1] - v[i][l],{x,{l+1,r}}});
	if(x && b[i][x-1] < l-1) {
		r = l; l = b[i][x-1]; x --;
		q[i].push({t.FI + v[i][l+1] - v[i][l],{x,{l+1,r}}});
	}
	if(q[i].empty()) return ;
	a[i].push_back(q[i].top().FI - t.FI);
	return ;
}
int id[MAXN],cn;
bool cmp(int x,int y) {
	return a[x].front() < a[y].front();
}
int main() {
	freopen("knapsack.in","r",stdin);
	freopen("knapsack.out","w",stdout);
	n = read(); m = read(); k = read();
	for(int i = 1;i <= m;i ++) v[i] = {0};
	for(int i = 1;i <= n;i ++) {
		s = read(); o = read();
		v[s].push_back(o);
	}
	bool flag = 1;
	LL ans = 0;
	for(int i = 1;i <= m;i ++) {
		int le = v[i].size()-1;
		sort(v[i].begin(),v[i].end());
		s = read();o = read();
		o = min(o,le);
		LL aa = 0;
		if(s > o || !flag) {flag = 0;continue;}
		if(o < 1) continue;
		for(int j = s+1;j <= o;j ++) b[i].push_back(0);
		for(int j = 1;j <= s;j ++) b[i].push_back(j),aa += v[i][j];
		q[i].push({aa,{o-1,{b[i][o-1],v[i].size()}}});
		nexp(i); ans += aa;
		if(!a[i].empty()) id[++ cn] = i;
	}
	sort(id + 1,id + 1 + cn,cmp);
	if(cn) P.push({ans + a[id[1]][0],{1,0}});
	if(!flag) {
		ans = -1;
		while(!P.empty()) P.pop();
	}
	for(int i = 1;i <= k;i ++) {
		AIput(ans,'\n');
		if(P.empty()) ans = -1;
		else {
			auto t = P.top();P.pop();
			ans = t.FI;
			int x = t.SE.FI,y = t.SE.SE;
			if(x < cn && !y) {
				P.push({t.FI+a[id[x+1]][0]-a[id[x]][0],{x+1,0}});
			}
			if(y+1 == (int)a[id[x]].size()) nexp(id[x]);
			if(y+1 < (int)a[id[x]].size()) {
				P.push({t.FI+a[id[x]][y+1],{x,y+1}});
			}
			if(x < cn) {
				P.push({t.FI+a[id[x+1]][0],{x+1,0}});
			}
		}
	}
	return 0;
}

后记

输出树上的前 K K K 大路径也是同一类问题,可以用边分治给状态分组,在边分树上枚举两个节点转移,但是一条路径可能在优先队列中紧挨着出现两次,状态只用两个点唯一标识,所以可用 set 代替优先队列简单去重一下。这就是为什么我在“方案不重复”后面写个“大概”。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值