洛谷2593 [ZJOI2006]超级麻将——可行性dp
题目:https://www.luogu.org/problemnew/show/P2593
发现三个连续牌的影响范围只有3、相同牌的影响范围只有1之后就可以dp了。
O(100^7)T飞。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N=105; int T,a[N]; bool dp[2][N][N][N],flag; int rdn() { int ret=0;char ch=getchar(); while(ch>'9'||ch<'0')ch=getchar(); while(ch>='0'&&ch<='9')(ret*=10)+=ch-'0',ch=getchar(); return ret; } int main() { T=rdn(); while(T--) { for(int i=1;i<=100;i++)a[i]=rdn(); for(int t=1;t<=100;t++)if(a[t]>=2) { a[t]-=2; memset(dp[1],0,sizeof dp[1]); dp[1][a[1]][a[2]][a[3]]=1; for(int i=3;i<=102;i++) { flag=0;int d=(i&1); memset(dp[!d],0,sizeof dp[!d]); for(int x=0;x<=a[i-2];x++) for(int y=0;y<=a[i-1];y++) for(int z=0;z<=a[i];z++) if(dp[d][x][y][z]) { int lm=min(x,min(y,z)); for(int k=0;k<=lm;k++) { dp[!d][y-k][z-k][a[i+1]]|=(((x-k)%3==0)|((x-k)%4==0)); if(dp[!d][y-k][z-k][a[i+1]])flag=1; } } if(!flag)break; } a[t]+=2; if(flag)break; } if(flag)printf("Yes\n");else printf("No\n"); } return 0; }
如果不枚举对子在哪而是带一个0/1在状态里的话可以少乘一个100。
如果不无脑记录前两个位置而是仅记录前一个位置、用三连的时候调用一下上一层的话可以少乘一个100。
这是因为最后要的只有dp[ ][0][0][0],所以保存很多“之前位置还不是0”的状态比较浪费。
不枚举用了几个三连,而是每层用“从开头降到这里”更新一下本层,就能把”在本层用较少的三连“包括在“用单个位置从之前层降下来”。这样可以少乘一个100。
这里调了好久!必须是“从开头降到这里”,而不能是“从这里降到0”;因为从“这里”降的话需要判断本层是否可行,但这个可行是包括 i-2 的状态的,自己仅记录前一个位置的dp不能存下。如果是”从开头降到这里“的话,本层目前一定是可行的。
O(100^4)卡过。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N=110; int T,a[N]; bool dp[N][N][N][2]; int rdn() { int ret=0;char ch=getchar(); while(ch>'9'||ch<'0')ch=getchar(); while(ch>='0'&&ch<='9')(ret*=10)+=ch-'0',ch=getchar(); return ret; } int main() { T=rdn(); while(T--) { memset(dp,0,sizeof dp); for(int i=1;i<=100;i++)a[i]=rdn(); dp[1][0][a[1]][0]=1; for(int i=1;i<=100;i++) { for(int x=a[i-1];x>=0;x--) for(int y=a[i];y>=0;y--) { dp[i][x][y][1]|=dp[i][x][y+2][0]; dp[i][x][y][0]|=dp[i][x][y+3][0]; dp[i][x][y][1]|=dp[i][x][y+3][1]; dp[i][x][y][0]|=dp[i][x][y+4][0]; dp[i][x][y][1]|=dp[i][x][y+4][1]; //在a[i]变成各种y之后尝试一下从开头降到这里(先降后单个弄) dp[i][x][y][0]|=dp[i-1][a[i]-y][x+a[i]-y][0]; dp[i][x][y][1]|=dp[i-1][a[i]-y][x+a[i]-y][1]; //在a[i]变成各种y之后尝试一下取完 // dp[i][x][0][0]|=dp[i-1][y][x+y][0];//不能&dp[i][x+y][y],因含义是i-2==0 // dp[i][x][0][1]|=dp[i-1][y][x+y][1];//不&的话会出错:0 3 0(原为2) //因为从当前降到0的话,需要判断当前是否可行 //可是降还需要i-2的也许有值,与表示“当前可行”的dp冲突,即无法表示当前是否可行 //而“从开头”降的话,一定可行 //x之所以不是0,是因为这里用到dp[i-1][y][][] if(!x) { dp[i+1][y][a[i+1]][0]|=dp[i][0][y][0]; dp[i+1][y][a[i+1]][1]|=dp[i][0][y][1]; } // if(i<=3) // { // if(dp[i][x][y][0])printf("dp[%d][%d][%d][%d]=%d\n",i,x,y,0,dp[i][x][y][0]); // if(dp[i][x][y][1])printf("dp[%d][%d][%d][%d]=%d\n",i,x,y,1,dp[i][x][y][1]); // } } } puts(dp[100][0][0][1]?"Yes":"No"); } return 0; }