1 条题解

  • 0
    @ 2026-5-4 18:02:55

    P8166 [eJOI2021] Kpart 题解

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

    思路

    暴力做法

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

    正解

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

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

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

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

    时间复杂度 O(Tnai)O(Tn\sum a_i)

    代码

    qzhqzh 表示前缀和数组。

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

    ff 与上文意思相同。

    码风较丑勿喷。

    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

    信息

    ID
    10879
    时间
    1000ms
    内存
    512MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者