P1357 花园

洛咕原题

题解

状压dp+矩乘

首先看到题目说M<=5,这么小的数据明显可以用状压保存相邻状态,于是可以得到一个80分的dp:

先筛出所有可用的状态,然后建立一个矩阵保存可转移的状态,再然后把每个状态都当成最初状态各跑一次dp,累计答案

然而我们发现,n太大了。又发现,其实每次转移可以直接用矩乘来搞(用到了状态矩阵)

于是就用矩乘了。嗯,就这样,具体看题解吧。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const int mod=1000000007;
ll n,ans;
int m,k,cnt;
int state[65];
struct matrix{
    int a[65][65];
    matrix(){memset(a,0,sizeof(a));}
    matrix operator * (matrix &tmp){
        matrix c;
        for(int i=1;i<=cnt;++i)
            for(int j=1;j<=cnt;++j)
                for(int k=1;k<=cnt;++k)
                    c.a[i][j]=(ll)(c.a[i][j]+(ll)a[i][k]*tmp.a[k][j]%mod)%mod;
        return c;
    }
    matrix ksm(matrix x,ll y){
        matrix ans;
        for(int i=1;i<=cnt;++i) ans.a[i][i]=1;
        for(;y;y>>=1){
            if(y&1) ans=ans*x;
            x=x*x;
        }
        return ans;
    }
}st,mul;

int main(){
    scanf("%lld%d%d",&n,&m,&k);
    for(int i=(1<<m)-1;i>=0;--i){
        int t=0;
        for(int j=i;j;j>>=1) t+=(j&1);
        if(t<=k) state[++cnt]=i;
    } //筛出可用状态
    for(int i=1;i<=cnt;++i)
        for(int j=1;j<=cnt;++j)
        {
            int x=state[i],y=state[j]>>1,ok=1;
            for(int k=m-1;k;--k){
                if((x&1)!=(y&1)) {ok=0; break;}
                x>>=1; y>>=1;
            }
            mul.a[i][j]=ok;
        } //可转移状态矩阵
    for(int i=1;i<=cnt;++i) st.a[i][i]=1;
    mul=mul.ksm(mul,n);
    st=st*mul;
    for(int i=1;i<=cnt;++i) ans=(ans+st.a[i][i])%mod; //累计矩阵对角线上的答案
    printf("%lld",ans);
    return 0;
}
原文地址:https://www.cnblogs.com/kafuuchino/p/9531139.html