1. 程式人生 > 實用技巧 >[CF743E] Vladik and cards

[CF743E] Vladik and cards

[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;
}