P8166 [eJOI2021] Kpart 题解

P8166 [eJOI2021] Kpart 题解

洛谷博客

模拟赛爆零的一道题。挺不错的一道题。

思路

暴力做法

先枚举 k k k ,然后枚举每个区间,用背包判断能不能取到。

正解

先枚举右端点 r r r,然后用 f i f_i fi 表示当前想要从 a 1 … a r a_1\dots a_r a1ar 中选取若干个数和为 i i i 时,左端点的最大值。这样就意味着你无法从 a f i + 1 … a r a_{f_i+1}\dots a_r afi+1ar 中找出和为 i i i 的部分。(这部分可能比较难以理解,建议多读几遍体会一下。)

然后暴力枚举左端点 l l l,令 s = ∑ i = l r a i s=\sum _{i=l} ^r a_i s=i=lrai(可以用前缀和方快速求出 s s s),判断是否满足以下条件:

  • 2 ∣ s 2|s 2∣s,只有这样的话才有可能能够将区间分成和相同的两半。

  • f s 2 ≤ l f_\frac{s}{2}\leq l f2sl,只有这样才意味着能够从这个区间中找出一个和为 s 2 \frac{s}{2} 2s 的部分,也就是能将它分成和相等的两半。

时间复杂度 O ( T n ∑ a i ) O(Tn\sum a_i) O(Tnai)

代码

q z h qzh qzh 表示前缀和数组。

a n s i = 0 ans_i=0 ansi=0 表示区间长度为 i i i 符合题意,反之亦然。

f f f 与上文意思相同。

码风较丑勿喷。

int T,n,a[1005],f[100005],qzh[1005];bool ans[1005];vector<int>v;
int main(){
	scanf("%d",&T);
	while(T--){
		v.clear();
		for(int i=1;i<=n;i++)ans[i]=0;
		for(int i=0;i<=(qzh[n]>>1);i++)f[i]=0;//初始化,也可用memset。
		scanf("%d",&n);
		for(int i=1;i<=n;i++)scanf("%d",&a[i]),qzh[i]=qzh[i-1]+a[i];
		for(int i=1;i<=n;i++){
			for(int j=min(qzh[i],qzh[n]>>1);j>a[i];j--)f[j]=max(f[j],f[j-a[i]]);
			f[a[i]]=i;//求 f 数组,过程类似于背包。
			for(int j=1;j<=i;j++)if(((qzh[i]-qzh[j-1])&1)||f[(qzh[i]-qzh[j-1])>>1]<j)ans[i-j+1]=1;//暴力枚举左端点。
		}
		for(int i=1;i<=n;i++)
			if(!ans[i])v.push_back(i);
		printf("%llu ",v.size());//注意 v.size() 类型是 unsigned long long。
		for(auto i:v)printf("%d ",i);
		puts("");
	}
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值