[Luogu2540][NOIP2015]斗地主增强版(搜索+DP)
增强版就是原版中两鬼不算对子的版本。
先爆搜出完所有对子,剩下的牌DP处理。
考虑每个数码的拆牌情况,最多可能被拆成5种情况:1+1+1+1,1+1+2,1+3,2+2,4。故DP状态数最多为5^13≈12e8,事实上远远不满。
而爆搜部分看上去就跑的挺快,具体复杂度玄学。
几个降低代码复杂度的方法和注意点:
1.双王不算对,2不算顺子。
2.1当14处理,2和双王单独处理。
3.对于DP状态,先拆牌后打出。
4.炸弹当三带一打。
5.DP用记忆化搜索实现,递归边界:既没有3也没有4,这样转移就只需考虑3和4的拆牌和打出了。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 5 using namespace std; 6 7 const int N=30,inf=1e9; 8 const int lim[4]={0,5,3,2}; 9 int T,n,x,y,a[N],b[N],f[N][N][N][N]; 10 11 int calc(int a,int b,int c,int d){ 12 if (a<0 || b<0 || c<0 || d<0) return inf; 13 if (!c && !d) return a+b; 14 if (~f[a][b][c][d]) return f[a][b][c][d]; 15 int ans=min(calc(a+1,b,c+1,d-1),calc(a,b+2,c,d-1));//拆四 16 ans=min(ans,min(calc(a+3,b,c-1,d),calc(a+1,b+1,c-1,d)));//拆三 17 ans=min(ans,min(calc(a-2,b,c,d-1),calc(a,b-1,c,d-1))+1);//四带一 18 ans=min(ans,min(calc(a,b-2,c,d-1),calc(a,b,c,d-2))+1);//四带二 19 ans=min(ans,min(calc(a-1,b,c-1,d),calc(a,b-1,c-1,d))+1);//三带一 20 return f[a][b][c][d]=ans; 21 } 22 23 int dfs(int x){ 24 int ans=inf; 25 rep(k,1,3) rep(i,3,14){ 26 int tot=0; 27 rep(j,i,14) if (a[j]>=k) tot++; else break; 28 rep(j,i+lim[k]-1,i+tot-1){ 29 rep(l,i,j) a[l]-=k; 30 ans=min(ans,dfs(x+1)); 31 rep(l,i,j) a[l]+=k; 32 } 33 } 34 b[1]=b[2]=b[3]=b[4]=0; 35 rep(i,2,14) b[a[i]]++; 36 if (a[0]==2) ans=min(ans,x+calc(b[1],b[2],b[3],b[4])+1);//火箭 37 ans=min(ans,x+calc(b[1]+a[0],b[2],b[3],b[4])); 38 return ans; 39 } 40 41 int main(){ 42 freopen("lord.in","r",stdin); 43 freopen("lord.out","w",stdout); 44 scanf("%d%d",&T,&n); memset(f,-1,sizeof(f)); 45 while(T--){ 46 memset(a,0,sizeof(a)); 47 rep(i,1,n) scanf("%d%d",&x,&y),a[x]++; 48 a[14]=a[1]; printf("%d\n",dfs(0)); 49 } 50 return 0; 51 }