A

模拟

问能否删除一些字符后重新排列,能组成给定的字符串。

可以排列,所以只要传力每种字符的出现次数,都比目标串更多即可,这可以统计,然后枚举目标串的

void solve(){
	int n;
	cin>>n;
	string s="Kato_Shoko";
	string t;
	cin>>t;
	
	map<char,int>mp1,mp2;
	for(char c:s)mp1[c]++;
	for(char c:t)mp2[c]++;
	bool ok=1;
	for(auto &[k,v]:mp1){
		if(mp2[k]<v)ok=0;
	}
	if(ok){
		cout<<"YES "<<t.size()-s.size();
	}
	else{
		cout<<"NO";
	}
}

B

贪心 排序

个真人,活跃度为,每个真人可以建立个复制,要使得总活跃度不少于,问最少需要复制多少次?

显然先复制较大的,经典贪心,如果当前真人全复制了也不够,就全复制,否则复制到刚好超过

void solve(){
	int n,k;
	cin>>n>>k;
	vi a(n+1),b(n+1);
	rep(i,1,n)cin>>a[i];
	rep(i,1,n)cin>>b[i];
	
	vvi c;
	rep(i,1,n){
		c.push_back({a[i],b[i]});
	}
	sort(c.begin(),c.end());
	reverse(c.begin(),c.end());
	int sum=0;
	rep(i,1,n){
		sum+=a[i];
	}
	if(sum>=k){
		cout<<0;
		return;
	}
	int ans=0;
	for(auto &t:c){
		int a=t[0],b=t[1];
		if(sum+a*b<=k){
			sum+=a*b;
			ans+=b;
		}
		else{
			ans+=(k-sum+a-1)/a;
			sum=k;
			break;
		}
	}
//	cout<<sum<<' '<<ans<<'\n';
	if(sum>=k){
		cout<<ans;
	}
	else{
		cout<<-1;
	}
}

C

枚举

问从一个数字组成的串李任选,组成一个整数,问能被整除的最小整数?

串很短,考虑枚举选中的数字集合,然后对他们枚举全排列,这样就是得到了全部可以得到的数字,可能带前导零,转换成整型可以直接

这里是枚举,也可以二进制枚举

void solve(){
	int n,k;
	cin>>n>>k;
	string s;
	cin>>s;
	
	string t;
	int ans=1e9;
	auto &&dfs=[&](auto &&dfs,int i)->void{
		if(i==n){
			int m=t.size();
			vi p(m);
			rep(i,0,m-1){
				p[i]=i;
			}
			do{
				string q;
				for(int x:p){
					q+=t[x];
				}
				if(!q.size())continue;
				int x=stoi(q);
				if(x%k==0){
					ans=min(ans,x);
				}
			}while(next_permutation(p.begin(),p.end()));
			return;
		}
		t.push_back(s[i]);
		dfs(dfs,i+1);
		t.pop_back();
		dfs(dfs,i+1);
	};
	dfs(dfs,0);
	if(ans<1e9){
		cout<<ans;
	}
	else{
		cout<<-1;
	}
}

D

位运算结论

每次选两个元素删掉,把加入集合,加入答案,直到集合之剩一个元素,问答案最小多少

需要知道

那么我们把所有元素都加起来就是

而我们进行这个操作过程中,就是把留在集合里,把加到答案,那么答案其实就是

操作顺序实际不影响答案,也就是最小是个诈骗

E

互异分拆数

分拆数就是把拆分成多个非负整数,且他们大小非严格递增的方案数。互异分拆数在此基础上要求严格递增,也就是所有数都不能相同

计算可以,注意到由于互异,和为,最多有项,那么可以把项数放在状态里,如果能转移,复杂度就是

也就是表示和为,有项的方案数。转移也就是要从规模较小的分拆数,映射到规模较大的分拆数的过程。一个显然的映射是,在最后加一个比原本最后更大的数,这个映射当然是对的,但是需要知道原本最后元素的值,这个信息只能放在状态里,导致状态数过多,不行。

另一个比较巧妙的映射是,分类讨论一个分拆的最小元素是不是1,如果是,把1删掉,然后把剩余元素减一,就得到了一个长度减一,和减的分拆,如果开头不是1,直接把所有元素减一,得到了一个长度仍未,和减的方案数。这个映射不需要知道最后一个元素的值,只用知道元素和,元素个数。

也就是

void solve(){
	int n,m;
	cin>>n>>m;
	
	vvi dp(n+1,vi(500));
	
	dp[0][0]=1;
	rep(i,1,n){
		rep(j,1,450){
			if(i-j<0)continue;
			if(j*(j+1)>2*n)continue;
			dp[i][j]=(dp[i-j][j-1]+dp[i-j][j])%M1;
		}
	}
	
	int ans=0;
	rep(i,1,450){
		ans=(ans+dp[n][i]*m%M1*qpow(m-1,i-1,M1)%M1)%M1;
	}
	cout<<ans;
}

F

划分子区间,每个子区间的贡献是区间元素的按位与,按位或的异或起来,总贡献是每个子区间的贡献加起来。问总贡献最小值

整体肯定是一个划分型表示完成了对长度的前缀的划分,的最优答案。但问题是可以从前面所有位置转移过来,

也就是

复杂度,必须加速转移

这种时候一般就要从这个的定义入手,如果是加减法,可以把所有含项用线段树维护,然后查询,这里的定义是按位与和按位或,想把他们加速到,思路则是,也就是按位与,按位或在元素增加时变化都是单调的,又由于二进制位只有个,最多只会变化次,每两次变化中间的都是一样的,只要取区间内的的即可,着最简单的做法是线段树,当然注意到是随不减的,所以最大值一定是每个区间的右端点。

由于有按位与,按位或两个,实际上会有个分界点,划分出个区间,每个区间作上述转移