NOIP2023模拟15联测36 均分财产

题目大意

有 n n n个数,你希望能删除其中不超过 k k k个数,然后将剩下的数划分为两个子集(可以有重复的数字),满足这两个子集的数的和是相等的。

为了降低出题和做题的难度,可以认为这 n n n个数在 1 1 1到 W W W内随机的。

2 ≤ n ≤ 2 × 1 0 5 , min ⁡ ( 25 , n − 2 ) ≤ k ≤ n − 2 , W = 2 × 1 0 5 2\leq n\leq 2\times 10^5,\min(25,n-2)\leq k\leq n-2,W=2\times 10^5 2≤n≤2×105,min(25,n−2)≤k≤n−2,W=2×105

题解

当 n ≤ 25 n\leq 25 n≤25时,枚举所有子集,找到元素和相同的集合 A A A和 B B B。如果 A A A和 B B B有交集,则两个集合都去掉交集的这部分,最终得到的两个集合即为题意所求。时间复杂度为 O ( n ) O(n) O(n)。

当 n > 25 n>25 n>25时,我们可以将这些数从小到大排序,取后 n − 25 n-25 n−25个数,从后往前扫(即要按数值从大到小扫)。维护两个集合的元素和之差 n o w now now:

  • 如果 n o w ≤ 0 now\leq 0 now≤0,则当前的 a i a_i ai给集合 A A A, n o w + = a i now+=a_i now+=ai
  • 如果 n o w > 0 now>0 now>0,则当前的 a i a_i ai给集合 B B B, n o w − = a i now-=a_i now−=ai

最后的 n o w now now一定满足 ∣ n o w ∣ ≤ W |now|\leq W ∣now∣≤W。

在剩下的 25 25 25个元素中,我们要找到两个集合,满足这两个集合的元素和之差为 n o w now now。

对于所有这 25 25 25个元素的子集,元素和都不超过 25 W 25W 25W,并且子集个数为 2 25 2^{25} 225,是比 26 W 26W 26W大很多的。又因为是选择两个子集使得两个子集的元素和之差等于 n o w now now,而且数据随机,所以几乎一定会出现两个集合的元素和之差为 n o w now now。求这两个集合的方法和 n ≤ 25 n\leq 25 n≤25时求答案方法类似。

时间复杂度为 O ( n + 2 25 ) O(n+2^{25}) O(n+225)。

code

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=200000;
int n,k,now=0,a[N+5],id[N+5],cnt[1<<25],sum[1<<25];
int z[N*30+5],ans1[N+5],ans2[N+5];
bool cmp(int x,int y){
	return a[x]<a[y];
}
int lb(int i){
	return i&(-i);
}
void solve(int s,int t){
	for(int i=1;i<=min(n,25);i++){
		if((s>>(i-1)&1)==(t>>(i-1)&1)) continue;
		if(s>>(i-1)&1) ans1[++ans1[0]]=i;
		else ans2[++ans2[0]]=i;
	}
}
void print(){
	printf("%d ",ans1[0]);
	for(int i=1;i<=ans1[0];i++) printf("%d ",id[ans1[i]]);
	printf("\n%d ",ans2[0]);
	for(int i=1;i<=ans2[0];i++) printf("%d ",id[ans2[i]]);
}
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);id[i]=i;
	}
	for(int i=0;i<=24;i++) cnt[1<<i]=i+1;
	if(n<=25){
		for(int s=1;s<1<<n;s++){
			sum[s]=sum[s^lb(s)]+a[id[cnt[lb(s)]]];
			if(z[sum[s]]){
				solve(z[sum[s]],s);
				print();
				return 0;
			}
			z[sum[s]]=s;
		}
		printf("-1");
		return 0;
	}
	sort(id+1,id+n+1,cmp);
	for(int i=n;i>=26;i--){
		if(now<=0){
			ans1[++ans1[0]]=i;
			now+=a[id[i]];
		}
		else{
			ans2[++ans2[0]]=i;
			now-=a[id[i]];
		}
	}
	for(int s=1;s<1<<25;s++){
		sum[s]=sum[s^lb(s)]+a[id[cnt[lb(s)]]];
		z[sum[s]]=s;
	}
	for(int s=1;s<1<<25;s++){
		int tmp=sum[s]+now;
		if(z[tmp]){
			solve(s,z[tmp]);
			print();
			return 0;
		}
	}
	printf("-1");
	return 0;
}
相关推荐
刺客xs2 分钟前
C++ 11新特性
java·开发语言·c++
..过云雨8 分钟前
【负载均衡oj项目】04. oj_server题目信息获取、界面渲染、负载均衡、后台交互功能
运维·c++·html·负载均衡·交互
..过云雨9 分钟前
【负载均衡oj项目】02. comm公共文件夹设计 - 包含所有需要用到的自定义工具
数据库·c++·mysql·html·负载均衡
知无不研21 分钟前
constexpr关键字
开发语言·c++·constexpr
2401_8980751222 分钟前
C++中的智能指针详解
开发语言·c++·算法
大头流矢27 分钟前
STL中的string容器和迭代器iterator
开发语言·c++
IOT-Power28 分钟前
Qt+C++ 控制软件架构实例
开发语言·c++·qt
草莓熊Lotso29 分钟前
Linux 进程间通信之命名管道(FIFO):跨进程通信的实用方案
android·java·linux·运维·服务器·数据库·c++
草莓熊Lotso30 分钟前
MySQL 表约束核心指南:从基础约束到外键关联(含实战案例)
android·运维·服务器·数据库·c++·人工智能·mysql
xiaoye-duck34 分钟前
《算法题讲解指南:递归,搜索与回溯算法--二叉树中的深搜》--10.二叉搜索树中第k小的元素,11.二叉树的所有路径
c++·算法·深度优先·递归