麻將 hongmah

題目來源:CF1110D
【簡要題意】有n個數集合(多重集),每個數不超過m。可以分成{i,i,i}或{i-1,i,i+1}的三元組,求最多分成幾份三元組。n、m<=1e6

【分析】
考慮動態規劃,寫出一個Θ(n3)\Theta(n^3)的動歸方程,放下轉貪心。試了好幾種貪心策略都被hack,並且對於貪心證明毫無頭緒。
回到dp,結果是一種對於狀態定義的優化:定義f[i][j][k]表示{i-1,i,i+1}有j個{i,i+1,i+2}有k個。可以證明0<j,k<3。如果j或k有3個及以上,那麼可以歸類到3個{i,i,i}的形式。
轉移方程:f[i][k][l]=max0&lt;=i,j,k&lt;3(f[i][k][l],f[i1][j][k]+a[i]jkl3+l)f[i][k][l] = \max\limits_{0&lt;=i,j,k&lt;3} (f[i][k][l],f[i-1][j][k]+\frac{a[i]-j-k-l}{3}+l)
對於剩餘的i轉移成{i,i,i}的形式,主要是{i,i+1,i+2}的干擾。枚舉完和i有關的三個三元組之後自然可以將剩下的部分悉數轉移而不用擔心狀態並非最優了。
我認爲這道題的最大的特點就是動歸狀態的定義依賴於三個操作的個數,而並未操作後剩下的部分。這種思路對於那些“狀態”之間有所糾纏,而操作本身並不複雜的動歸題目的狀態定義有啓發意義。

【code】

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1e6+1000;
int n,m,a[maxn],cnt=0;
int f[maxn][3][3];
inline void read(int &x){
	x=0;int fl=1;char tmp=getchar();
	while(tmp<'0'||tmp>'9'){if(tmp=='-')fl=-fl;tmp=getchar();}
	while(tmp>='0'&&tmp<='9')x=(x<<1)+(x<<3)+tmp-'0',tmp=getchar();
	x=x*fl;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){int x;read(x);a[x]++;}
	memset(f,-1,sizeof(f));f[0][0][0]=0;
	for(int i=1;i<=m;i++)
		for(int j=0;j<3;j++)
			for(int k=0;k<3;k++)
				for(int l=0;l<3;l++)
				if(a[i]<j+k+l)continue;
				else f[i][k][l]=max(f[i][k][l],f[i-1][j][k]+(a[i]-j-k-l)/3+l);
	cout<<f[m][0][0]<<endl;
	return 0;
} 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章