洛谷 P1357 花园(状压DP+矩阵快速幂)

题目传送门

https://www.luogu.org/problemnew/show/P1357


思路

显然的状压DP

F[i][S]+=F[i1][S>>1]+F[i1][(S>>1)+(1<<(m1))]

转移的条件是状态皆合法,这个可以预处理。

直接递推肯定不行,我们开一个32*32的矩阵,同时有一个状态列向量,表示达到各个状态的方案数。

矩阵左乘列向量,矩阵(i,j)代表着j->i状态的转移。我们要破环为链,对应直接由初始1~m转移n次,求回到原来的状态的方案。一种暴力的方法是枚举1~m的初始状态,然后对每个状态转移n次统计答案,但这样的时间是O(32^4*log(10^15))的,不够优。

我们发现,每次枚举初始状态,相当于在初始向量的某一位填上1,我们直接将转移矩阵自乘n-1次,对角线的和就是所有的方案数。

时间复杂度O(32^3*log(10^15)),stable.


代码

#include 
#define MOD 1000000007

using namespace std;

typedef long long LL;
LL n, ans;
int m, K, cnt;
bool Legal[32];

struct Mat{
    LL a[32][32];
    void Clear(){
        memset(a, 0LL, sizeof(a));
    }
    friend Mat operator * (Mat A, Mat B){
        Mat C;
        C.Clear();
        for(int i = 0; i < cnt; i++)
            for(int j = 0; j < cnt; j++)
                for(int k = 0; k < cnt; k++)
                    C.a[i][j] = (C.a[i][j] + A.a[i][k] * B.a[k][j] % MOD) % MOD;
        return C;
    }
};

Mat Pow(Mat X, LL Y){
    Mat Z = X;
    while(Y){
        if(Y & 1)  Z = X * Z;
        X = X * X;
        Y >>= 1;
    }
    return Z;
}

int main(){

    scanf("%lld%d%d", &n, &m, &K);

    cnt = 1 << m;

    Mat F;
    F.Clear();

    for(int i = 0; i < cnt; i++){
        int temp = 0;
        for(int j = 0; j < m; j++)  if((1<if(temp <= K)  Legal[i] = true;
    }

    for(int i = 0; i < cnt; i++){
        if(Legal[i] && Legal[i>>1])  F.a[i][i>>1] = 1;
        if(Legal[i] && Legal[(i>>1)+(1<<(m-1))])  F.a[i][(i>>1)+(1<<(m-1))] = 1;
    }

    F = Pow(F, n-1);

    for(int i = 0; i < cnt; i++)
        ans = (ans + F.a[i][i]) % MOD;

    printf("%lld\n", ans);

    return 0;
} 

你可能感兴趣的:(DP,&,记忆化搜索,矩阵乘法,快速幂)