题面
题解
我永远讨厌dp.jpg
搞了一个下午优化复杂度最后发现只要有一个小trick就可以A了→_→。全场都插头dp就我一个状压跑得贼慢……
不难发现我们可以状压,对于每一行,用状态\(S\)表示有哪些格子是已经被上一行推倒了的,那么我们可以枚举本行所有格子的字母情况,然后计算一下这个时候下一行格子被推倒的情况,把这一行的贡献加到下一行就行了。
简单来说就是记一个\(f[pos][S]\)表示第\(pos\)行,格子被推倒的情况为\(S\)时的方案数,\(dp[pos][S]\)为所有方案中推倒树的总数,那么假设一个选字母的方案会使下一行的推倒情况为\(S'\),会使这一行可以推倒\(k\)棵树,则有转移\[f[pos+1][S']+=f[pos][S]\]
\[dp[pos+1][S']+=f[pos][S]+k\times f[pos][S]\] 最后\(f[n+1][0]\)就是答案。这样的话能有\(40\)分(建议先看一下40分代码不然看不太懂AC代码的……)//minamoto#include#define R register#define fp(i,a,b) for(R int i=a,I=b+1;i I;--i)#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)using namespace std;const int N=13,M=35,L=(1<<21)+5;int a[N][M],f[N][L],dp[N][L],g[N][L],n,m,P,lim,ans,vis[N];inline int add(R int x,R int y){return x+y>=P?x+y-P:x+y;}inline int mul(R int x,R int y){return 1ll*x*y-1ll*x*y/P*P;}void solve(int pos){ fp(i,0,lim-1)if(f[pos][i]){ fp(j,0,lim-1){ int S=0,res=0; fp(k,0,m-1)vis[k]=i&(1<
然后我们发现复杂度高的主要原因是因为行数太多,不过列数很少,那么我们可以对列进行状压。然而这样的话会不符合推倒的顺序。
我们考虑每一条副对角线,这条副对角线上肯定是从右上到左下的推倒顺序,于是我们可以对每一条副对角线进行状压,因为副对角线上元素个数为\(min(n,m)\),所以时间复杂度没问题
信心满满的交上去结果只有\(70\)分,因为按上面那种方式枚举行的推倒情况和行的字母不太好,对于那些已经被推倒的格子,它们不管怎么选都没有影响,所以我们可以只枚举那些没有被推倒的格子,那些已经被推倒的格子直接把贡献加上去就可以了,这样的话复杂度就是\(O(3^n\times\)乱七八糟的常数\()\)
还是一句话,注意细节
//minamoto#include#define R register#define fp(i,a,b) for(R int i=a,I=b+1;i I;--i)#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)using namespace std;const int N=55,M=35,L=(1<<12)+5;int a[N][M],f[N][L],dp[N][L],n,m,P,ans,vis[N];int id[N][M],sz[L],bin[N];inline int add(R int x,R int y){return x+y>=P?x+y-P:x+y;}inline int mul(R int x,R int y){return 1ll*x*y-1ll*x*y/P*P;}void solve(int pos){ int cnt=pos-max(0,pos-n)-max(0,pos-m); int stx,sty,edx,edy,dx,dy; if(pos<=m)stx=pos,sty=1; else stx=m,sty=pos-m+1; if(pos<=n)edx=1,edy=pos; else edx=pos-n+1,edy=n; int qaq=pos+1>m,c=pos+1-max(0,pos+1-n)-max(0,pos+1-m); int lim=(1< >1]+(i&1); bin[0]=1;fp(i,1,30)bin[i]=mul(bin[i-1],2); f[1][0]=1,dp[1][0]=0; fp(i,1,n+m-2)solve(i); printf("%d\n",mul(add(dp[n+m-1][0],dp[n+m-1][1]),2));}