花园 状压DP+矩阵快速幂

花园

Solution:

如果您敏锐的注意到了M的范围,那么不难想到状压。

状态压缩一下最后M位的放的花盆的情况。

同时还可以提前与处理一下V[i,j]表示状态i能否转移到状态j。

但是,鉴于它是一个环形的,所以似乎并不好怎么去DP(断环为链似乎也不好弄)。

所以此处用的转移还是特别巧妙的。

不妨这样来看

如果从一个状态开始,顺次经过n次DP后,又回到了当前状态,就代表这形成了一个环!

那么,可以直接枚举前M个每一种可行的状态,DP到n+M,把和最初状态相同的那个状态的DP值加入ans即可

BF Code:

IL void DP(int state) {
    RG int i,j,k;
    memset(f,0,sizeof(f));
    f[m][state]=1;
    for(i=m+1;i<=n+m;++i)
        for(j=0;j<=(1<<m)-1;++j) {
            if(!ok[j]) continue;
            for(k=0;k<=(1<<m)-1;++k)
                if(ok[k]&&v[k][j]) (f[i][j]+=f[i-1][k])%=mod;
        }
    (ans+=f[n+m][state])%=mod;
}

主函数中:
for(i=0;i<=(1<<m)-1;++i)
        if(ok[i]) DP(i);

这样可以做到80分。

实际上这一DP转移的过程同样可以看做是矩阵乘法。

怎么说?

不妨把f看做状态矩阵,v看做转移矩阵。

可以发现,当v[x,y]为1时,就对应 从f[i-1,x] 给 f[i,y] 累计贡献,

那么在矩阵乘法中,就相当于 f[1,y] 的更新,会来自于所有的 v[x,y]*f[1,x]。

所以考虑矩阵快速幂加速。

因为暴力DP中,也就可行的初始态的f值为1。

所以直接将转移矩阵v自乘n次,对于每一种可行的初始态i,把矩阵中的MT[i,i]加入答案即可。

Code :

#include<string>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define RG register
#define IL inline
#define int long long
#define DB double
using namespace std;

IL int gi() {
    char ch=getchar(); RG int x=0,w=0;
    while(ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}
    while(ch>='0'&&ch<='9') x=x*10+(ch^48),ch=getchar();
    return w?-x:x;
}

const int N=33;
const int mod=1e9+7;

int n,m,K,ans,ok[N],v[N][N];

struct Matrix {
    int MT[N][N];
    Matrix(){memset(MT,0,sizeof(MT));}
    IL void NewMT() {
        RG int i;
        for(i=0;i<N;++i) MT[i][i]=1;
    }
    Matrix operator *(const Matrix &s) {
        RG int i,j,k,w=(1<<m)-1;
        RG Matrix ans;
        for(i=0;i<=w;++i)
            for(j=0;j<=w;++j)
                for(k=0;k<=w;++k)
                    (ans.MT[i][j]+=MT[i][k]*s.MT[k][j]%mod)%=mod;
        return ans;
    }
}f;

IL Matrix qpow(Matrix x,int p) {
    RG Matrix ans;
    for(ans.NewMT();p;p>>=1,x=x*x)
        if(p&1) ans=ans*x;
    return ans;
}

IL bool check(int x) {
    RG int cnt=0;
    while(x) cnt+=x&1,x>>=1;
    return cnt>K;
}

IL void getv(int x) {
    RG int y=x>>1;
    if((x>>m)&1) x^=1<<m;
    if(check(y)||check(x)) return;
    ok[x]=ok[y]=1,v[x][y]=1;
}

void dfs(int x,int now) {
    if(x==m+2) {getv(now);return;}
    dfs(x+1,now),dfs(x+1,now|(1<<x-1));
}

signed main()
{
    RG int i,j,w;
    n=gi(),m=gi(),K=gi();
    w=(1<<m)-1,dfs(1,0);    
    for(i=0;i<=w;++i)
        for(j=0;j<=w;++j) f.MT[j][i]=v[i][j];
    // 注意!!! 由于v记录的是i可以转移到j.所以要反过来
    // 实际上,这是矩阵乘法的特点所造成的.
    for(i=0,f=qpow(f,n);i<=w;++i)
        if(ok[i]) (ans+=f.MT[i][i])%=mod;
    printf("%lld\n",ans);
    return 0;
}

转载于:https://www.cnblogs.com/Bhllx/p/10655518.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值