• 花园


    花园


    也是一道不错的状压练习题,梳理一遍思路


    首先,很明显,我们并不能够用一个简洁组合式子表示原问题答案。我们想到了递推计数。

    不难想到,由于这个是一个环,我们拆环,将其转化成序列上的问题。同时,我们枚举该序列上最后(m)个数,分别对枚举的这些情况一一统计答案。

    具体来讲,我们可以令(dp(i,S))为前(i)个数后(m)位状态为(S)的方案个数:

    [dp(i,S)=dp(i-1,S>>1)+dp(i-1,(S>>1)|(1<<m-1)) ]

    这里,我们只考虑(S)合法的情况。

    注意到该方程前一维仅涉及两个位置的状态值,因此滚动数组优化空间:

    const int S = 1 << 6, mod = 1000000000 + 7;
    int dp[2][S], ans = 0;
    
    for(int S0 = 0; S0 < 1 << m; ++ S0)
    {
        if(!valid[S0]) continue;//预处理状态
        memset(dp, 0, sizeof dp);
        dp[0][S0] = 1;
        for(int i = 1; i <= n; ++ i)
        {
            for(int s = 0; s < 1 << m; ++ s)
            {
                if(!valid[s]) continue;
                bool op = i & 1;
                dp[op][s] = (dp[1 - op][s >> 1] + dp[i - op][(s >> 1) | (1 << m - 1)]) % mod;  
            }
        }
        ans = (ans + dp[n][S0]) % mod;
    }
    printf("%d
    ", ans);
    

    接着发现(n)太大了,需要矩阵加速递推。

    我们可以考虑,对于一个状态矩阵:

    (egin{bmatrix} dp(0) & dp(1) & … & dp(2^{m-1}-2) & dp(2^{m-1}-1)end{bmatrix})

    可以构造一个转移矩阵使其能够递推。

    考虑到:对于转移矩阵而言,新的状态值仅依赖于不超过两个状态,因此我们可以对于每一个状态计算出来。

    最后发现,我们由于枚举些许状态拆环,我们可以直接将初始的矩阵构造成这个样子,答案为每个合法状态(mat(k,k))

    (egin{bmatrix} dp(0) & … & … \… & dp(1) & … &\…\…& …end{bmatrix})

    …
    typedef long long LL;
    
    const int N = 1e5 + 5, S = 40, mod = 1e9 + 7;
    
    bool valid[S] = {};
    LL n;
    int m, k, ans = 0, dp[2][S], mat[S][S] = {};
    int calc(int x)
    {
    	int res = 0;
    	while(x)
    	{
    		++ res;
    		x -= lowbit(x);
    	}
    	return res;
    }
    
    void mul(int (*c)[S], int (*a)[S], int (*b)[S])
    {
    	static int res[S][S];
    	CLR(res, 0);
    	for(int i = 0; i < 1 << m; ++ i)
    		for(int j = 0; j < 1 << m; ++ j)
    			for(int p = 0; p < 1 << m; ++ p)
    				res[i][j] = (res[i][j] + 1ll * a[i][p] * b[p][j] % mod) % mod;
    			
    	memcpy(c, res, sizeof res);
    }
    
    void power(int (*c)[S], int (*a)[S], LL b)
    {
    	static int res[S][S];
    	CLR(res, 0);
    	for(int i = 0; i < 1 << m; ++ i) res[i][i] = 1;
    	while(b)
    	{
    		if(b & 1) mul(res, res, a);	
    		b >>= 1;
    		mul(a, a, a);
    	}
    	memcpy(c, res, sizeof res);
    	return;
    }
    
    int main()
    {
    	scanf("%lld %d %d", &n, &m, &k);
    	for(int s = 0; s < 1 << m; ++ s) valid[s] = calc(s) <= k;
    	
    	for(int i = 0; i < 1 << m; ++ i)
    	{
    		int state = (i >> 1) | (1 << m - 1);
    		mat[i >> 1][i] = 1, mat[state][i] = valid[state];
    	}
    	power(mat, mat, n);
    	for(int i = 0; i < 1 << m; ++ i) if(valid[i]) ans = (ans + mat[i][i]) % mod;
    	printf("%d
    ", ans);
    	return 0;
    } 
    

    总结:

    1. 对付环状问题的技巧一般有两个:分类讨论和拆环;
    2. 如果递推的时候发现迭代过多,应当考虑矩阵加速处理该问题。
  • 相关阅读:
    二维码生成插件(jquery.qrcode.js)说明文档
    JS&PHP如何实现二维码的生成以及识别(代码)
    【干货】Chrome插件(扩展)开发全攻略 写在前面
    电脑连接并调试手机浏览器的网页
    php操作mysql数据库(增删改查)
    springBoot+springCloud学习笔记
    HttpClient远程调用接口
    fastjson List<> 转Json , Json 转List<>
    连接redis失败,关闭防火墙即可
    复习mybatis框架(一)----映射文件
  • 原文地址:https://www.cnblogs.com/zach20040914/p/14404267.html
Copyright © 2020-2023  润新知