CodeForces743E. Vladik and cards 二分+状压dp
这个题我们可以想象成_---___-----__的一个水柱它具有一遍优一遍行的性质因此可以用来二分最小值len,而每次二分后我们都要验根,we可以把这个水柱想成我们在每个数段里取前一段的那个数后一段有也不选,而且最后一个区间的第一个数一定可以使这个数区间对应的数,那么我们只要在某个位置上不选或选就可以啦,这we很容易想到2n的验根但是这样还不如打暴力,这时我们发现这个dfs是低效的他会很多进入等效状态这样的话我们就可以记忆化搜索了,那么何妨递推,由于我们知道f[pos][mask]中只要pos(位置),mask(二进制代表是否选过)确定那么就要他的最大值就行了,而状态转移分两种一是前面len或len+1,或前一个状态,那么只要每一个状态是最大值,那么我们就从前面推到最后就找到了,这样的话我们可以正向推来代替反向吸收,因为如果只有那样吸收也就只有那样推.
千万不要忘了0的特判以及状态转移的完全性.
时间复杂度o(8*log2n*28*n)
#include<cstdio> #include<vector> #include<cstring> #include<iostream> #include<algorithm> #define MAXN 1001 using namespace std; int f[MAXN][(1<<8)+10]; int n,a[MAXN],full=(1<<8)-1; bool had[10]; vector<int>pos[10]; inline void pre() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); had[a[i]]=1; pos[a[i]].push_back(i); } } inline int Max(int x,int y) { return x>y?x:y; } inline int get(int p,int len) { int now=lower_bound(pos[a[p]].begin(),pos[a[p]].end(),p)-pos[a[p]].begin(); int ans=now+len-1; if(pos[a[p]].size()-1<ans)return -1; return pos[a[p]][ans]; } int judge(int len) { memset(f,0,sizeof(f)); for(int i=0;i<n;i++) { int to=get(i+1,len); if(to!=-1)f[to][(1<<(a[i+1]-1))]=Max(f[i][0]+len,f[to][(1<<(a[i+1]-1))]); to=get(i+1,len+1); if(to!=-1)f[to][(1<<(a[i+1]-1))]=Max(f[i][0]+len+1,f[to][(1<<(a[i+1]-1))]); for(int j=1;j<full;j++) if(f[i][j]) { f[i+1][j]=Max(f[i+1][j],f[i][j]); if(j&(1<<(a[i+1]-1)))continue; to=get(i+1,len); if(to!=-1)f[to][j|(1<<(a[i+1]-1))]=Max(f[i][j]+len,f[to][j|(1<<(a[i+1]-1))]); to=get(i+1,len+1); if(to!=-1)f[to][j|(1<<(a[i+1]-1))]=Max(f[i][j]+len+1,f[to][j|(1<<(a[i+1]-1))]); } f[i+1][full]=Max(f[i+1][full],f[i][full]); } return f[n][full]; } void work() { int ans=0,l=1,r=n>>3; for(int i=1;i<=8;i++) if(had[i])ans++; while(l<=r) { int mid=(l+r)>>1,x=judge(mid); ans=Max(x,ans); if(x) l=mid+1; else r=mid-1; } printf("%d",ans); } int main() { pre(); work(); return 0; }
苟利国家生死以, 岂因祸福避趋之。