5月CWOI杂题

xx019 / 2023-05-03 / 原文

C0238 【0503 B组】模拟测试

垫底。

A 【1026 B组】bins

有一个结论:将两部分各自排序后对应装入是最优的,因为假如有交叉的选择,你交换后一定不劣。

发现 \(m\le1000\),可以把数列丢到值域上用后缀和判断。因为需要严格大于,可以挪一位。

code:

我写了线段树,其实不用。

#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int inf=1e18;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
	return x*f;
}
int m,n,a[20005];
int cntA[20005],cntB[20005],A[20005],B[20005];
struct segtree{
	#define ls p<<1
	#define rs p<<1|1
	#define lson l,mid,ls
	#define rson mid+1,r,rs
	struct Node{
		int mi,tag;
	}c[80005];
	void pushup(int p){
		c[p].mi=min(c[ls].mi,c[rs].mi);
	}
	void pushdown(int l,int r,int p){
		if(!c[p].tag)return;
		c[ls].mi+=c[p].tag,c[rs].mi+=c[p].tag;
		c[ls].tag+=c[p].tag,c[rs].tag+=c[p].tag;
		c[p].tag=0;
	}
	void build(int l,int r,int p){
		c[p].tag=0;
		if(l==r){c[p].mi=B[l]-A[l];return;}
		int mid=(l+r)>>1;
		build(lson);build(rson);
		pushup(p);
	}
	void update(int l,int r,int p,int L,int R,int k){
		if(L>R)return;
		if(L<=l&&r<=R){
			c[p].mi+=k,c[p].tag+=k;
			return;
		}
		int mid=(l+r)>>1;pushdown(l,r,p);
		if(L<=mid)update(lson,L,R,k);
		if(R>mid)update(rson,L,R,k);
		pushup(p);
	}
	int query(int l,int r,int p,int L,int R){
		if(L>R)return 0;
		if(L<=l&&r<=R){
			return c[p].mi;
		}
		int mid=(l+r)>>1,res=inf;pushdown(l,r,p);
		if(L<=mid)res=min(res,query(lson,L,R));
		if(R>mid)res=min(res,query(rson,L,R));
		return res;
	}
}Tr;
signed main(){
	m=read(),n=read();
	for(int i=1;i<=n;i++)a[i]=read(); 
	for(int i=1,j=n/2+1;i<=n/2;i++,j++)cntA[a[i]]++,cntB[a[j]]++;
	for(int i=m;i>=1;i--)A[i]=A[i+1]+cntA[i];
	for(int i=m-1;i>=1;i--)B[i]=B[i+1]+cntB[i+1];
	Tr.build(1,m,1);
	for(int k=n/2;k>=1;k--){
		if(Tr.query(1,m,1,1,m)>=0)return printf("%lld\n",k),0;
		Tr.update(1,m,1,1,a[k],1);
		Tr.update(1,m,1,1,a[k]-1,1);
		Tr.update(1,m,1,1,a[2*k-1]-1,-1);
		Tr.update(1,m,1,1,a[2*k]-1,-1);
	}
	puts("0");
	return 0;
}

B 【1026 B组】inversions

还没调出来。

C 【1026 B组】candies

有一种较为暴力的做法:枚举我们要改的位置,对剩下的做一个背包,那么加入的物品一定会使得背包为 1 的位置变为两倍(不看 0)。要最大化答案,可以先找出最大的方案,再枚举加入物品的体积,过程可以用 bitset 优化。

(注意:下文中提到“为 0/1 的位置”均指的是在最优的方案中,不考虑会改变的元素,剩余的做背包的那个数组。)

提一嘴:虽然这个做法不太能过,但加上一个强力剪枝能过:发现对于为 1 的位置的集合 \({i_1,i_2\ldots i_s}\) 和为 0 的位置的集合 \({j_1,j_2\ldots j_t}\)(满足 \(j_1>i_1\),也就是 \(i_1\) 以前的为 0 的位置不管),\(i_k\) 在左移后必须安排到一个为 0 的位置,也就是至少为 \(j_k\)。我们枚举移动步数时可以利用这个结论跳一下,不用一个一个试。这样虽然挺玄学(我没写),但好像几乎卡不掉。

正解是你发现左移的步数 x 必须满足对于任意为 1 的位置 \(i<j\) 都要满足 \(j-i\neq x\)。有一个神奇的写法:

B.reset();B[0]=1;
for(int j=1;j<=n;++j)if(id!=j)B|=(B<<b[j]);
for(int j=1;j<=n;++j)if(id!=j)B|=(B>>b[j]);

最后 B 中不为 1 的就是可行的答案。为什么这是对的?因为 \(j-i\) 这个式子可以看作有 \(n\) 个体积为 \(b_i\)\(n\) 个体积为 \(-b_i\) 的物品做背包。

虽然能过,但还是有点慢,怎么优化?你可以先对所有物品跑背包,每次在这个基础上撤销掉这个物品的影响,得到的就是其余的背包。但是我们需要记录方案数,存不下怎么办?可以对大数取模,相信概率。

code:

//每次暴力背包
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}
bitset<1386005>B;int b[105],cnt[105];
signed main(){
	int n=read(),id=1,s=0;
	for(int i=1;i<=n;++i)b[i]=read(),s+=b[i];
	sort(b+1,b+n+1);
	for(int i=1;i<=n;++i){
		if(b[i]==b[i-1])continue;
		B.reset();B[0]=1;
		for(int j=1;j<=n;++j)if(i!=j)B|=(B<<b[j]);
		cnt[i]=(int)B.count();
		if(cnt[i]>cnt[id])id=i;
	}
	B.reset();B[0]=1;
	for(int j=1;j<=n;++j)if(id!=j)B|=(B<<b[j]);
	for(int j=1;j<=n;++j)if(id!=j)B|=(B>>b[j]);
	for(int i=0;i<=1386000;++i)if(B[i]==0)return printf("%lld %lld\n",b[id],i),0;
	return 0;
}
//优化
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int b[105],s[105],cnt[105],f[700005],g[700005];bitset<700005>B;
signed main(){
	int n,id=1,all=1;scanf("%d",&n);f[0]=1;for(int i=1;i<=n;++i)scanf("%d",&b[i]);
	sort(b+1,b+n+1);for(int i=1;i<=n;++i)s[i]=s[i-1]+b[i];
	for(int i=1;i<=n;++i)for(int j=s[i];j>=b[i];--j){
		if(f[j]!=0)all--;
		f[j]=(f[j]+f[j-b[i]])%mod;
		if(f[j]!=0)all++;
	}
	for(int i=1;i<=n;++i){
		if(b[i]==b[i-1])continue;
		cnt[i]=all;
		memcpy(g,f,sizeof(f));
		for(int j=b[i];j<=s[n];++j){
			if(g[j]!=0)cnt[i]--;
			g[j]=(g[j]-g[j-b[i]]+mod)%mod;
			if(g[j]!=0)cnt[i]++;
		}
		if(cnt[i]>cnt[id])id=i;
	}
	B.reset();B[0]=1;
	for(int j=1;j<=n;++j)if(id!=j)B|=(B<<b[j]);
	for(int j=1;j<=n;++j)if(id!=j)B|=(B>>b[j]);
	for(int i=1;i<=s[n]-b[id]+1;++i)if(B[i]==0)return printf("%d %d\n",b[id],i),0;
	return 0;
}

D 【1026 B组】sheep

还不会。