[CF743E] Vladik and cards
阿新 • • 發佈:2020-07-30
[CF743E] Vladik and cards
一.前言
把子序列看成子串還真是對不起了。題目連結
二.思路
首先由每兩個數字出現的次數之差不超過1可以知道,以下幾點。對於一個可以記入答案的序列,有
- 所有的數字都在裡面(除非部分數字只選一個,其餘不選)
- 所有出現的數字的出現次數之中有一個最小值 k
- 有部分數字的出現次數為 k+1
然後我們試著看一看k和答案的關係,假如設 add 為出現次數為 k+1 的數字個數,那麼很顯然的有 \(ans=8*k+add\).並且有 ans和k成正相關的關係。
有了以上的結論之後,我們可以二分猜k,然後試圖計算出在完成每個數字至少出現k個的標準時,add的值。若是無法達到標準,那麼就將k下調,否則上升。
現在只需要解決如何求add的問題。這裡使用狀壓DP,\(f[x][j]\)表示在x的位置 j 所代表的數字都已經出現至少 k 次的add。然後需要一個輔助變數,\(pos[x][i]\)表示序列中第 i 個 x 的位置。這裡用了一小點貪心的思想,即如果我們要選一些相同的數出來,那麼這些數肯定是靠的越近越好,這樣才能為後面的DP提供更多的機會。
那麼轉移方程可以比較清楚的推出,這裡寫不清楚,還是看程式碼會比較容易。需要注意的是,每次進行轉移的時候,我們是先選出狀態以外的一個數 p,然後得出這個 p 之後第 k-1 的 p的位置,轉移到那上面去。(這樣總共就有k個p),然後如果後面還剩的有 k 個 p(即總可塞入的有k+1個)
如何得到之後第 k-1 個 p 的位置是依靠於pos陣列,但是每次不可能從\(pos[p][1]\) 開始往後面算,那麼再用一個 start 陣列表示應該是從 \(pos[p][start[p]]\) 開始找 k-1 個,即求出 \(pos[p][start[p]+k-1]\),注意\(start\)隨著i而變化。
最後初始化\(f[0][0]=0\)就可以輕鬆解決掉這道題啦!
三.CODE
#include<iostream> #include<cstdio> #include<algorithm> #include<fstream> #include<cmath> #include<cstring> using namespace std; int read(){ char ch=getchar(); int res=0,f=1; for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1; for(;ch>='0'&&ch<='9';ch=getchar())res=res*10+(ch-'0'); return res*f; } const int MAXN=(1<<8)-1; int n,a[1005],t[9],pos[9][1005],r=1<<15,l; int start[9],f[1005][MAXN+5],ans; inline int maxx(int x,int y){ return x>y?x:y; } bool check(int x){ for(int i=1;i<=8;++i)start[i]=1;//初始化 memset(f,250,sizeof(f)); f[0][0]=0; for(int i=0;i<n;++i){ for(int j=0;j<MAXN;++j) if(f[i][j]>=0)//用它去更新 for(int k=1;k<=8;++k){//選一個數 if(j&(1<<(k-1)))continue; int u=start[k]+x-1,g=j|(1<<(k-1)); if(u<=t[k])f[pos[k][u]][g]=maxx(f[pos[k][u]][g],f[i][j]); if(++u<=t[k])f[pos[k][u]][g]=maxx(f[pos[k][u]][g],f[i][j]+1);//可以選出x+1個 } ++start[a[i]];//更新,每次選的數都要在i及其以後 } int add=-1; for(int i=8;i<=n;++i)add=maxx(add,f[i][MAXN]);//前7個不可能有答案 if(add==-1)return 0; ans=maxx(ans,8*x+add); return 1; } int main(){ n=read(); for(int i=1;i<=n;++i){ a[i]=read(); pos[a[i]][++t[a[i]]]=i;//記錄位置 } for(int i=1;i<=8;++i)r=min(r,t[i]); if(r==0){//特判是否有數字沒有,那麼就是括號內的特殊情況 int res=0; for(int i=1;i<=8;++i)if(t[i])res++; cout<<res; return 0; } while(l<=r){//二分猜k int mid=(l+r)>>1; if(check(mid))l=mid+1; else r=mid-1; } printf("%d",ans); return 0; }