首页 > 其他分享 >CF1374E2 Reading Books(hard version) 题解

CF1374E2 Reading Books(hard version) 题解

时间:2023-09-07 14:56:44浏览次数:46  
标签:CF1374E2 val int 题解 tree pos version ls ab

CF1374E2 Reading Books(hard version)

这道题是在 CF1374E1 Reading Books(easy version) 的基础上出的,而且仅仅增加了一个 \(m\) 的限制,下面的做法也是基于简单版的思路而想到的。建议先尝试简单版,然后尝试此题。

题意简述:

有 \(n\) 本书,每本书有一个阅读时间 \(t_i\),和属性 \(a_i\)、\(b_i\),代表是否被 Alice 和 Bob 喜欢。要求两人恰好读 \(m\) 本书,其中必须有至少 \(k\) 本被 Alice 喜欢,至少 \(k\) 本被 Bob 喜欢,求最小的总阅读时间,并输出读书方案。总阅读时间为这 \(m\) 本书的时间和。

思路:

简单版

先不考虑 \(m\)。我们可以把书分为四类:被 Alice 喜欢,被 Bob 喜欢,两人都喜欢,两人都不喜欢。我们设这四类分别为 \(a\)、\(b\)、\(ab\)、\(none\)。显然,两人都不喜欢的书没必要去选,满足了 \(k\) 的限制后,也没必要去多选书。因此,问题转换为了要选多少 \(ab\) 与 \(a\)、\(b\)。每多选一本 \(ab\) 就可以少选一本 \(a\) 和和一本 \(b\),所以我们可以枚举选 \(i\) 本 \(ab\),那么 \(a\) 和 \(b\) 的数量就是 \(k-i\)。我们只需要知道阅读时间前 \(i\) 小的 \(ab\) 的阅读时间和以及前 \(k-i\) 小的 \(a\) 和 \(b\) 的阅读时间和即可。这三类书的选择相互独立,因此可以分别排序,求前缀和即可。

困难版

和简单版唯一的不同在于,简单版可以读任意数量的书,而困难版必须读 \(m\) 本。也就是说,如果已经满足了 \(k\) 的限制后,读的书数量不够多,必须从未选择的书中再选出几本。

我们来分析一下刚才过程中,未选择的书有哪些。还是设选择 \(i\) 本 \(ab\),设 \(ab\)、\(a\)、\(b\)、\(none\) 四种书的总数为 \(totab\)、\(tota\)、\(totb\)、\(totn\)。

  • \(ab\) 中有 \(totab - i\) 本未被选择;
  • \(a\) 中有 \(tota - k + i\) 本书未被选择;
  • \(b\) 中有 \(totb - k + i\) 本书未被选择;
  • \(none\) 中的我们都没有选。

我们设没有选的书的集合为 \(S\),那么我们只需要找到 \(S\) 中阅读时间前 \(m-k*2+i\) 小的书的阅读时间和。这个可以用平衡树做。

我们来理一下这个过程。首先从小到大枚举选 \(i\) 本 \(ab\),然后维护 \(S\) 内的元素,计算答案,如果答案可以更新,则记录一下得到答案时 \(i\) 是多少(因为还要输出方案)。

至于输出方案,首先 \(ab\)、\(a\)、\(b\) 都好处理,从小到大输出即可;对于 \(S\) 中我们选择的元素,可以先恢复 \(S\) 在得到答案的 \(i\) 时的状态,然后把得到答案的那部分输出出来即可。

总时间复杂度 \(O(n \log n)\)。细节见代码注释。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2e5+100;

inline int read(){
	int x = 0; char ch = getchar();
	while(ch<'0' || ch>'9') ch = getchar();
	while(ch>='0'&&ch<='9') x = x*10+ch-48, ch = getchar();
	return x;
}

struct xwx {
	ll val;
	int id;int v;
	bool operator < (const xwx &b ) const{
		return val < b.val;
	}
};// 用来存储 a、b、ab、none 中的书
struct qwq{
	int v, id;
	bool operator <(const qwq &b) const{
		if(v == b.v){
			return id < b.id;
		}
		return v < b.v;
	}
	bool operator >(const qwq &b) const{
		if(v == b.v){
			return id > b.id;
		}
		return v >b.v;
	}
};//作为 FHQ 上元素的权值,重载运算符便于删除操作。
xwx a[N], b[N], ab[N], no[N];//A喜欢,B喜欢,都喜欢,都不喜欢。

struct node{
	qwq val;
	ll sum;//sum 记录子树内的权值和。
	int siz;
	int ls, rs;
	int rnd;
};
int root;
mt19937 getrand(time(0));
struct FHQ_Treap{
	node tree[N];
	int idx;
	#define ls(x) tree[x].ls
	#define rs(x) tree[x].rs
	void push_up(int tr){
		tree[tr].sum = tree[ls(tr)].sum+tree[rs(tr)].sum+tree[tr].val.v;
		tree[tr].siz = tree[ls(tr)].siz+tree[rs(tr)].siz+1;
	}
	int New(qwq val){
		tree[++idx] = (node){val, val.v, 1, 0, 0, (int)getrand()};
		return idx;
	}
	void split_by_size(int pos, int &l, int &r, int K){//按大小分裂,也就是取出前 K 小的元素
		if(!pos) return l = r = 0, void();
		int tmp = tree[ls(pos)].siz+1;
		if(K>=tmp){
			l = pos;
			split_by_size(rs(pos), rs(pos), r, K-tmp);
			push_up(l);
		} else{
			r = pos;
			split_by_size(ls(pos), l, ls(pos), K);
			push_up(r);
		}
	}
	void split_by_val(int pos, int &l, int &r, qwq K){//按权值大小分裂,主要用于插入元素。
		if(!pos) return l = r = 0, void();
		if(K>tree[pos].val){
			l = pos;
			split_by_val(rs(pos), rs(pos), r, K);
			push_up(l);
		} else{
			r = pos;
			split_by_val(ls(pos), l, ls(pos), K);
			push_up(r);
		}
	}
	int merge(int l, int r) {
		if((!l) || (!r)) return l | r;
		if(tree[l].rnd < tree[r].rnd){
			rs(l) = merge(rs(l), r);
			push_up(l);
			return l;
		} else{
			ls(r) = merge(l, ls(r));
			push_up(r);
			return r;
		}
	}
	void insert(int val, int id){
		int dl, dr;
		split_by_val(root, dl, dr, (qwq){val, id});
		root = merge(merge(dl, New((qwq){val, id})), dr);
	}
	void del(int val, int id){
		int dl, md, dr;
		split_by_val(root, dl, dr, (qwq){val, id});
		split_by_val(dr, md, dr, (qwq){val, id+1});//将目标节点分裂出来,合并两边。
		root = merge(dl, dr);
	}
	ll query(int K){
		int dl, dr;
		split_by_size(root, dl, dr, K);//分裂出前 K 小的元素。
		ll tmp = tree[dl].sum;
		root = merge(dl,dr);
		return tmp;
	}
	void clear(){
		root = 0;
		idx = 0;
		memset(tree, 0, sizeof(tree));
	}
	void output(int pos){
		if(ls(pos)){
			output(ls(pos));
		}
		printf("%d ", tree[pos].val.id);
		if(rs(pos)){
			output(rs(pos));
		}
	}//遍历子树,输出方案
	void print(int K){
		int dl,dr;
		split_by_size(root, dl, dr, K);
		if(dl) output(dl);
	}
}s;
int ta, tb, tab, tn;
int n, m, K;
int main(){
	n = read(), m = read(), K = read();
	for(int i = 1, ar, br, t; i<=n; ++i){
		t = read(), ar = read(), br = read();
		if(ar && br){
			ab[++tab] = (xwx){t, i, t};
			s.insert(t, i);
		} else if(ar){
			a[++ta] = (xwx){t, i, t};
		} else if(br){
			b[++tb] = (xwx){t, i, t};
		} else{
			no[++tn] = (xwx){t, i, t};
			s.insert(t, i);//没人喜欢的直接插入到 S 里
		}
	}
	sort(a+1, a+ta+1);
	sort(b+1, b+tb+1);
	sort(ab+1, ab+tab+1);
	// sort(no+1, no+tn+1);
	for(int i = 1; i<=ta; ++i){
		a[i].val+=a[i-1].val;
	}
	for(int i = 1; i<=tb; ++i){
		b[i].val+=b[i-1].val;
	}
	for(int i = 1; i<=tab; ++i){
		ab[i].val+=ab[i-1].val;
	}
	// 求前缀和。
	ll ans = 0x3f3f3f3f3f3f3f3f;
	int num = -1;//记录答案在哪里产生
	int tota = ta, totb = tb, totab = tab;
	for(int i = 0; i<=min(K, tab); ++i){
		if(i > 0){
			s.del(ab[i].v, ab[i].id);
		}//枚举到的 ab 必须要选,所以就从 S 中删除。
		if((K-i)>ta || (K-i)>tb || K*2-i > m) continue;

		while(ta > (K-i)){
			s.insert(a[ta].v, a[ta].id);
			--ta;
		}
		while(tb > (K-i)){
			s.insert(b[tb].v, b[tb].id);
			--tb;
		}//将选不上的 a 和 b 都加入 S

		ll tmp = ab[i].val + a[ta].val + b[tb].val + s.query(m-K*2+i);
		if(ans > tmp){
			num = i;
			ans = tmp;
		}
	}
	if(ans == 0x3f3f3f3f3f3f3f3f){
		puts("-1");
		return 0;
	}
	printf("%lld\n", ans);
	s.clear();
	for(int i = num+1; i<=totab; ++i){
		s.insert(ab[i].v, ab[i].id);
	}
	for(int i = (K-num+1); i<=tota; ++i){
		s.insert(a[i].v, a[i].id);
	}
	for(int i = (K-num+1); i<=totb; ++i){
		s.insert(b[i].v, b[i].id);
	}
	for(int i = 1; i<=tn; ++i){
		s.insert(no[i].v, no[i].id);
	}//将 S 恢复到 i = num 时的状态

	for(int i = 1; i<=num; ++i){
		printf("%d ", ab[i].id);
	}
	for(int i = 1; i<=K-num; ++i){
		printf("%d ", a[i].id);
	}
	for(int i = 1; i<=K-num; ++i){
		printf("%d ", b[i].id);
	}//顺序输出 ab、a、b
	s.print(m-K*2+num);//输出 S 中选择的元素。
	return 0;
}

标签:CF1374E2,val,int,题解,tree,pos,version,ls,ab
From: https://www.cnblogs.com/frostwood/p/17684907.html

相关文章

  • Eclipse里做JBPM工作流gpd.xml中文乱码问题解决
         刚开始接到是做一个简单的文档借阅流程,对于流程定义是采用eclipse中的jbpm插件,但存在一个问题是节点中文命名的在gpd.xml中全部为乱码或根本看不到任何东西。     但是网上有人说没关系,这只是eclipse本身存在的一个bug,在项目所在硬盘目录下打开该文件还是显示正常......
  • 题解 [NOIP2018 提高组] 赛道修建
    题目链接挺综合的一道题目。询问最小值最大,考虑二分最小值,二分上下界是\([最小边权,树的直径]\),但是为了方便我们直接设为\([1,5\times10^8]\)即可。考虑如何\(check\),可以采用类似树形\(dp\)的方式进行贪心。对于节点\(u\)的子树,\(u\)内部的点显然可以构成几条链,同......
  • 【PCL】使用kdtree时pop_t报错问题解决
    问题描述在使用kdtree时,遇到的报错,具体报错信息如下:提示找不到pop_t,点击错误信息会进入到dist.h文件中问题解决解决办法很简单,在这里简单总结一下进入dist.h文件中,将下面这行代码,应该是在源文件的503行:typedefunsignedlonglongpop_t;具体位置如下图所示:将这行代码......
  • FTP权限问题解析,553 Can't open that file: Permission denied
    FTP权限问题解析,553Can'topenthatfile:Permissiondenied FTP上传文件,提示553Can'topenthatfile:Permissiondenied原因:目录的所属组,所属用户属于root,导致FTP无法上传,修改组和所属用户为www即可chown-fRwww./*chgrp-fRwww./* ......
  • How to tell which version of HW your Tesla Model 3 is using All In One
    HowtotellwhichversionofHWyourTeslaModel3isusingAllInOne如何判断你的TeslaModel3使用的是那个版本的HWHW3.5vsHW4AutopilotHardwareVersionNamesThehardwarenamingcanbeabitconfusingsincethey’vebeencalleddifferentthings......
  • 9.6 CF1830 题解
    9.6CF1830题解A.CopilCopacDrawsTrees链接真弱智题不用讲B.TheBOSSCanCountPairs题意每组数据给你一个\(n\)和两个序列\(a,b\)。求有多少个数对\((i,j)\)满足\(1\lei<j\len\)且\(a_i\timesa_j=b_i+b_j\)。题解考虑\(a_i\timesa_j=b_i......
  • 【题解】CF2600DP 选练(23.9.5-23.9.6)
    低情商:感觉是比较套路的高情商:十分educational!!!CF258DLittleElephantandBrokenSorting题目描述:有一个\([1,n]\)的排列\(a\),会进行\(m\)次操作,操作为交换\((a_i,a_j)\)。每次操作都有\(50\%\)的概率进行。求进行\(m\)次操作以后的期望逆序对个数。\(n,m\le1......
  • 爱思创CSP第一轮模拟赛01易错题解析
    一.1.错误原因:不知道解析:正确答案B星型结构,类似于一颗星星,优点是节省材料,弊端是,如果源点计算机故障,那么网络就会瘫痪。环形结构,类似于一个环,环上有一些端点,每个端点对应着一台计算机,弊端是,如果在环上断了2条边,网络就会瘫痪网状结构,就是现在的因特网(Internet),类似于一张图,......
  • $9.6$ 短学期题解
    \(a\)inta[N];voidsolve(){intn=read();for(inti=1;i<=n;i++){a[i]=read();}sort(a+1,a+1+n);intans=0;for(inti=1;i<=min(5,n);i++){if(a[i]<=300)ans++;}puts(ans>=5?"PentaKill&qu......
  • Iksevi 题解
    题目大意\(n\)次询问,每次给定一个点\((x,y),x\ge0,y\ge0\),问有多少种对角线长为偶数的正方形使得在用该正方形正密铺第一象限的情况下该点位于正方形顶点上。正密铺第一象限指将第一个正方形的角与\(x\)轴和\(y\)轴接触。此后的正方形都与至少一个已放置的正方形有一......