1. 程式人生 > >CodeForces743E. Vladik and cards 二分+裝壓dp

CodeForces743E. Vladik and cards 二分+裝壓dp

inline max scanf nbsp sca pre ++ cnblogs scan

這個題我們可以想象成_---___-----__的一個水柱它具有一遍優一遍行的性質因此可以用來二分最小值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; }

CodeForces743E. Vladik and cards 二分+裝壓dp