https://codeforces.com/contest/1582/problem/F1
题意
给n个数字,让你任意选出一些子集,使得子集内数字严格递增,问所有子集的异或和的可能值。
n<=100000 , 数字大小ai <= 500
朴素思路
考虑暴力的解法:设dp[i][j]代表子集的最后一个数字(同时也是子集最大值)为i,子集的异或和为j,状态是否存在。
因为数字小于500,所以异或和最大不超过1024
for(int i = 1 ; i <= n ; i++){
cin >> num;
for(int j = 0 ; j < num ; num++){
for(int k = 0 ; k < 1024 ; k++){
if(dp[j][k]){
dp[num][k ^ num] = 1;
}
}
}
}
面向范围编程,可知复杂度大致应为O(n * 1024),上面的复杂度并不能接受。
优化
可以发现如果dp[5][6] = 1, dp[4][6] = 1.
那么转移的时候能通过dp[5][6]转移而来的状态必然也能从dp [4][6]转移。
所以对于某一种异或和,只需要关心能产生它最小的子集最大值。
改进dp状态:dp[i]代表异或和为i的所有子集中min(子集最大值)。
AC源码
#include<cstdio>
#include<iostream>
using namespace std;
const int maxm = 1 << 10;
const int inf = 1e9;
int dp[maxm];//dp[x]代表 能异或出x的最小数字
int main(){
int n;
cin >> n;
for(int i = 0 ; i < maxm ; i++)dp[i] = inf;
dp[0] = 0;
int cnt, res;
for(int i = 1 ; i <= n ; i++){
cin >> cnt;
for(int j = 0 ; j < maxm ; j++){
if(dp[j] < cnt){//保证递增
res = j ^ cnt;//异或后的数字
dp[res] = min(dp[res], cnt);
}
}
}
int num = 0;
for(int i = 0 ; i < maxm ; i++)if(dp[i] != inf)num++;
cout << num << endl;
for(int i = 0 ; i < maxm ; i++){
if(dp[i] != inf)cout << i << ' ';
}
cout << endl;
}