• 花园


    花园


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


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

    不难想到,由于这个是一个环,我们拆环,将其转化成序列上的问题。同时,我们枚举该序列上最后(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. 如果递推的时候发现迭代过多,应当考虑矩阵加速处理该问题。
  • 相关阅读:
    文本比较算法Ⅴ——回顾贴,对前面几篇文章的回顾与质疑
    键盘监控的实现Ⅱ——容易产生误解的CallNextHookEx函数
    利用WPF建立自适应窗口大小布局的WinForm窗口
    计算机中的颜色XIII——颜色转换的快速计算公式
    键盘监控的实现Ⅲ——按键消息的修改(映射)
    计算机中的颜色XI——从色相值到纯色的快速计算(新的公式)
    Dot Net中InputLanguage对象的使用限制
    计算机中的颜色XII——快速计算纯色的色相值(新的公式)
    关于房产中介网的设计随想
    java笔记:熟练掌握线程技术基础篇之解决资源共享的问题(中)前篇
  • 原文地址:https://www.cnblogs.com/zach20040914/p/14404267.html
Copyright © 2020-2023  润新知