题目
长度为n的序列,第i个值为ai,
easy:n<=1e5,0<=ai<=500
hard:n<=1e6,0<=ai<=5000
对于序列中的所有严格上升子序列,
每一种序列的值异或起来,得到一个值v,
输出v的种类数,按增序输出所有v的可能性
思路来源
quality代码(hard)
题解
easy:
暴力bitset维护可达集,每次暴力异或,1e5*500*500/64
dp[i]表示当前能异或出i的序列最小值
hard:
dp[i][j]表示当前序列最尾为i时,子序列能异或出j时,子序列末尾值i的最小位置
实际实现时,由于需要根据前缀转移,实际需要不超过i
心得
感觉还是用到了严格上升子序列的本质,
要么是子序列最后一个值尽可能小,
要么是达到相同的值的时候位置尽可能靠前
代码1(easy)
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
using namespace std;
typedef long long ll;
const int N=515,INF=0x3f3f3f3f;
int ans[N],c;
int n,now[N],v;
int main(){
memset(now,INF,sizeof now);
now[0]=0;
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",&v);
for(int j=0;j<512;++j){
if(now[j]<v){
now[j^v]=min(now[j^v],v);
}
}
}
for(int j=0;j<512;++j){
if(now[j]<INF){
ans[++c]=j;
}
}
printf("%d\n",c);
for(int i=1;i<=c;++i){
printf("%d%c",ans[i]," \n"[i==c]);
}
return 0;
}
代码2(hard)
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <bitset>
using namespace std;
const int N=5e3+1,M=8192,INF=0x3f3f3f3f;
//dp[i][j]:子序列尾<=i时, 能异或出j的最小的序列位置
int n,v,dp[N][M],ans[M],c;
vector<int>pos[N];
int main(){
memset(dp,INF,sizeof dp);
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",&v);
pos[v].push_back(i);
}
dp[0][0]=0;
for(int i=1;i<N;++i){
for(int j=0;j<M;++j){
dp[i][j]=dp[i-1][j];
if(pos[i].empty())continue;
if(i==j){
dp[i][j]=min(dp[i][j],pos[i].front());
}
int id=upper_bound(pos[i].begin(),pos[i].end(),dp[i-1][j^i])-pos[i].begin();
if(id!=pos[i].end()-pos[i].begin()){
dp[i][j]=min(dp[i][j],pos[i][id]);
}
}
}
for(int j=0;j<M;++j){
if(dp[N-1][j]!=INF){
ans[++c]=j;
}
}
printf("%d\n",c);
for(int i=1;i<=c;++i){
printf("%d%c",ans[i]," \n"[i==c]);
}
return 0;
}