https://www.luogu.org/problem/P2668
https://www.luogu.org/problem/P2540
做得崩溃,不过幸亏洛谷大大可以下载数据
首先看数据水一点点的
显然本题与花色无关,而且除了顺子,其余的都只和同种牌的出现次数有关(与牌的大小无关)
于是考虑$dp_{i,j,k,l}$表示出现$4$次的牌有$i$张,$3$次的牌有$j$张......时,在不打顺子的情况下,打完牌的最少次数
转移有点多,但是很好想,想清楚了就不会写错
那怎么处理顺子呢?
暴搜跑得还挺快
有两点需要注意:
1.顺子不一定一次性要打完,如3,4,5,6,7,8,8,8,8,9,9,11,11,这个样例若先打出3,4,5,6,7,再打出8,8,8,8,9,9,11,11,比先打出4,5,6,7,8,9要优
2.同样的牌也不一定非要以同样的形式打出,可以拆成一些单牌,如3,3,3,3,4,4,4,5,5,5先打出3,3,3,3,5,5,再打出4,4,4,5为最优解,把5拆开了
然后我们看数据加强版
其实就一个地方不一样:四带二里面“二”不能是双王
给$dp$数组加一维表示王的数量即可
然后转移的时候记得加一些东西
P2668
#include<bits/stdc++.h> using namespace std; int n,cnt[20]; int dp[20][20][20][20]; void cmin(int x,int &y) {y=x>y?y:x;} void prepare() { memset(dp,0x3f,sizeof(dp)); dp[0][0][0][0]=0; for(int i=0;i<=14;++i) for(int j=0;j<=14;++j) for(int k=0;k<=14;++k) for(int l=0;l<=14;++l) if(i*4+j*3+k*2+l<=n) { if(i>0) { //炸弹 cmin(dp[i-1][j][k][l]+1,dp[i][j][k][l]); //四带二 if(l>1) cmin(dp[i-1][j][k][l-2]+1,dp[i][j][k][l]); if(k>1) cmin(dp[i-1][j][k-2][l]+1,dp[i][j][k][l]); //拆开打1+1+1+1,1+1+2,2+2,1+3 cmin(dp[i-1][j][k][l+4],dp[i][j][k][l]); cmin(dp[i-1][j][k+1][l+2],dp[i][j][k][l]); cmin(dp[i-1][j][k+2][l],dp[i][j][k][l]); cmin(dp[i-1][j+1][k][l+1],dp[i][j][k][l]); } if(j>0) { //三张牌 cmin(dp[i][j-1][k][l]+1,dp[i][j][k][l]); //三带一 if(l>0) cmin(dp[i][j-1][k][l-1]+1,dp[i][j][k][l]); //三带二 if(k>0) cmin(dp[i][j-1][k-1][l]+1,dp[i][j][k][l]); //拆开打1+1+1,1+2 cmin(dp[i][j-1][k][l+3],dp[i][j][k][l]); cmin(dp[i][j-1][k+1][l+1],dp[i][j][k][l]); } if(k>0) { //对子与火箭 cmin(dp[i][j][k-1][l]+1,dp[i][j][k][l]); //拆开打1+1 cmin(dp[i][j][k-1][l+2],dp[i][j][k][l]); } //单张牌 if(l>0) cmin(dp[i][j][k][l-1]+1,dp[i][j][k][l]); } } int dfs(int step,int cow[]) { int ex[5]={0,0,0,0,0}; int now[20]; for(int i=1;i<=14;++i) now[i]=cow[i]; for(int i=1;i<=14;++i) ++ex[now[i]]; if(ex[0]==14) return 0; //不打顺子 int ret=dp[ex[4]][ex[3]][ex[2]][ex[1]]+step; //三顺子 for(int i=1;i<=12;++i) { int cc=0; for(int j=1;i+j-1<=12;++j) if(now[i+j-1]>=3) ++cc; else break; now[i]-=3; for(int j=2;j<=cc;++j) { now[i+j-1]-=3; cmin(dfs(step+1,now),ret); } now[i]+=3; for(int j=2;j<=cc;++j) now[i+j-1]+=3; } //双顺子 for(int i=1;i<=12;++i) { int cc=0; for(int j=1;i+j-1<=12;++j) if(now[i+j-1]>=2) ++cc; else break; now[i]-=2,now[i+1]-=2; for(int j=3;j<=cc;++j) { now[i+j-1]-=2; cmin(dfs(step+1,now),ret); } now[i]+=2,now[i+1]+=2; for(int j=3;j<=cc;++j) now[i+j-1]+=2; } //单顺子 for(int i=1;i<=12;++i) { int cc=0; for(int j=1;i+j-1<=12;++j) if(now[i+j-1]>=1) ++cc; else break; for(int j=0;j<4;++j) now[i+j]-=1; for(int j=5;j<=cc;++j) { now[i+j-1]-=1; cmin(dfs(step+1,now),ret); } for(int j=0;j<4;++j) now[i+j]+=1; for(int j=5;j<=cc;++j) now[i+j-1]+=1; } return ret; } int main() { // freopen("testdata.in","r",stdin); // freopen("testdata.out","w",stdout); int T; scanf("%d%d",&T,&n); prepare(); while(T--) { for(int i=1;i<=15;++i) cnt[i]=0; for(int i=1;i<=n;++i) { int a,b; scanf("%d%d",&a,&b); if(a>2) ++cnt[a-2]; else if(a) ++cnt[a+11]; else ++cnt[14]; } printf("%d\n",dfs(0,cnt)); } return 0; }
P2540
#include<bits/stdc++.h> using namespace std; int n,cnt[20]; int dp[20][20][20][20][3]; void cmin(int x,int &y) {y=x>y?y:x;} void prepare() { memset(dp,0x3f,sizeof(dp)); dp[0][0][0][0][0]=0; for(int i=0;i<=13;++i) for(int j=0;j<=13;++j) for(int k=0;k<=13;++k) for(int l=0;l<=13;++l) for(int s=0;s<=2;++s) if(i*4+j*3+k*2+l+s<=n) { if(i>0) { //炸弹 cmin(dp[i-1][j][k][l][s]+1,dp[i][j][k][l][s]); //四带二 if(l>1) cmin(dp[i-1][j][k][l-2][s]+1,dp[i][j][k][l][s]); if(s>1) cmin(dp[i-1][j][k][l][s-2]+1,dp[i][j][k][l][s]); if(k>1) cmin(dp[i-1][j][k-2][l][s]+1,dp[i][j][k][l][s]); if(s>0 && l>0) cmin(dp[i-1][j][k][l-1][s-1]+1,dp[i][j][k][l][s]); //拆开打1+1+1+1,1+1+2,2+2,1+3 cmin(dp[i-1][j][k][l+4][s],dp[i][j][k][l][s]); cmin(dp[i-1][j][k+1][l+2][s],dp[i][j][k][l][s]); cmin(dp[i-1][j][k+2][l][s],dp[i][j][k][l][s]); cmin(dp[i-1][j+1][k][l+1][s],dp[i][j][k][l][s]); } if(j>0) { //三张牌 cmin(dp[i][j-1][k][l][s]+1,dp[i][j][k][l][s]); //三带一 if(l>0) cmin(dp[i][j-1][k][l-1][s]+1,dp[i][j][k][l][s]); if(s>0) cmin(dp[i][j-1][k][l][s-1]+1,dp[i][j][k][l][s]); //三带二 if(k>0) cmin(dp[i][j-1][k-1][l][s]+1,dp[i][j][k][l][s]); //拆开打1+1+1,1+2 cmin(dp[i][j-1][k][l+3][s],dp[i][j][k][l][s]); cmin(dp[i][j-1][k+1][l+1][s],dp[i][j][k][l][s]); } if(k>0) { //对子 cmin(dp[i][j][k-1][l][s]+1,dp[i][j][k][l][s]); //拆开打1+1 cmin(dp[i][j][k-1][l+2][s],dp[i][j][k][l][s]); } //单张牌 if(l>0) cmin(dp[i][j][k][l-1][s]+1,dp[i][j][k][l][s]); //火箭 if(s>1) cmin(dp[i][j][k][l][s-2]+1,dp[i][j][k][l][s]); if(s>0) cmin(dp[i][j][k][l][s-1]+1,dp[i][j][k][l][s]); } } int dfs(int step,int cow[]) { int ex[5]={0,0,0,0,0}; int now[20]; for(int i=1;i<=14;++i) now[i]=cow[i]; for(int i=1;i<=13;++i) ++ex[now[i]]; //不打顺子 int ret=dp[ex[4]][ex[3]][ex[2]][ex[1]][now[14]]+step; //三顺子 for(int i=1;i<=12;++i) { int cc=0; for(int j=1;i+j-1<=12;++j) if(now[i+j-1]>=3) ++cc; else break; now[i]-=3; for(int j=2;j<=cc;++j) { now[i+j-1]-=3; cmin(dfs(step+1,now),ret); } now[i]+=3; for(int j=2;j<=cc;++j) now[i+j-1]+=3; } //双顺子 for(int i=1;i<=12;++i) { int cc=0; for(int j=1;i+j-1<=12;++j) if(now[i+j-1]>=2) ++cc; else break; now[i]-=2,now[i+1]-=2; for(int j=3;j<=cc;++j) { now[i+j-1]-=2; cmin(dfs(step+1,now),ret); } now[i]+=2,now[i+1]+=2; for(int j=3;j<=cc;++j) now[i+j-1]+=2; } //单顺子 for(int i=1;i<=12;++i) { int cc=0; for(int j=1;i+j-1<=12;++j) if(now[i+j-1]>=1) ++cc; else break; for(int j=0;j<4;++j) now[i+j]-=1; for(int j=5;j<=cc;++j) { now[i+j-1]-=1; cmin(dfs(step+1,now),ret); } for(int j=0;j<4;++j) now[i+j]+=1; for(int j=5;j<=cc;++j) now[i+j-1]+=1; } return ret; } int main() { // freopen("testdata.in","r",stdin);freopen("testdata.out","w",stdout); int T; scanf("%d%d",&T,&n); prepare(); while(T--) { for(int i=1;i<=15;++i) cnt[i]=0; for(int i=1;i<=n;++i) { int a,b; scanf("%d%d",&a,&b); if(a>2) ++cnt[a-2]; else if(a) ++cnt[a+11]; else ++cnt[14]; } printf("%d\n",dfs(0,cnt)); } return 0; }