Covering HDU - 6185 状压DP+高斯消元找规律 + 矩阵快速幂

Bob’s school has a big playground, boys and girls always play games here after school.?

To protect boys and girls from getting hurt when playing happily on the playground, rich boy Bob decided to cover the playground using his carpets.?

Meanwhile, Bob is a mean boy, so he acquired that his carpets can not overlap one cell twice or more.?

He has infinite carpets with sizes of?1×21×2?and?2×12×1, and the size of the playground is?4×n4×n.?

Can you tell Bob the total number of schemes where the carpets can cover the playground completely without overlapping??

Input

There are no more than 5000 test cases.?

Each test case only contains one positive integer n in a line.?

1≤n≤10181≤n≤1018?

Output

For each test cases, output the answer mod 1000000007 in a line.?

Sample Input

1
2
Sample Output

1
5

题意:给你一个N,让你用1x2的地毯去铺4xN的操场,不能有重叠,问你有多少种方案,结果对1000000007取模。

我们考虑这样一个问题:有一个W行H列的广场,需要用1*2小砖铺盖,小砖之间互相不能重叠,问有多少种不同的铺法?

我们按行去铺这个广场,每到一个方格,我们就有两种策略:铺或不铺。如果我们用01来表示的话,很容易就想到二进制。

我们就考虑用状压DP,我们可以用dp[i][j]来表示第i行状态为j的铺法数。

开始铺时有如下几种情况:
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
有如上几种情况,因此我们很容易写出转移方程,代码如下:

//对于一个当前行的可行状态s,用dfs构造不和它矛盾的下一行的状态,将方案累加到下一行。 
//所以每遇到一个可行状态,都要进行一遍dfs。
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
ll dp[20][15000];
int w,h;//有一个w行h列的矩阵
//dp[i][j]表示第i行状态是j的时候的方案数;
//j是一个二进制压缩的数,转换成二进制,每一位为0或1,0表示当前格子没被覆盖,1表示当前格子被覆盖了 
void dfs(int i,int state,int next,int c)
{
	//i表示当前在第i行,state表示第i行的状态,next表示第i+1行的状态,c表示现在在第c列 
	if(c==h)
	{
		dp[i+1][next]+=dp[i][state];
		return ;
	}
	if((state&(1<<c))==0)//如果从右往左数第c列没被覆盖过 
	{
		dfs(i,state,next|(1<<c),c+1);//竖着放 
		if(c+2<=h&&(state&(1<<(c+1)))==0)//如果从右往左数第c列没被覆盖过并且c+1列没被覆盖过
			dfs(i,state,next,c+2);//横着放 
	}
	else//如果从右往左数第c列覆盖过 
		dfs(i,state,next,c+1);//不放 
}
int main()
{
	while(~scanf("%d%d",&w,&h))
	{
		if((w*h)%2==1)//总格子数为奇数,用2x1的转怎么也铺不满 
		{
			printf("-1\n");
			continue;
		}
		memset(dp,0,sizeof(dp));
		dp[1][0]=1;
		for(int i=1;i<=w;i++)
			for(int j=0;j<(1<<h);j++)
				dfs(i,j,0,0);
		printf("%lld\n",dp[w+1][0]);
	}
	return 0;
}

好了,我们知道了这些,我们就很容易求出题目所给问题的前几项:
n ans
1 1
2 5
3 11
4 36
5 95
6 281
7 781
8 2245
9 6336
10 18061

我们光这么看肯定是看不出有啥规律的。

我们考虑各项之间的关系,假设f(n)=af(n-1)+bf(n-2),f(n)=af(n-1)+bf(n-2)+cf(n-3),f(n)=af(n-1)+bf(n-2)+cf(n-3)+d*f(n-4)…

我们可以想到,利用高斯消元来求解线性方程组,我们一直往下试,直到出现一组整数解,那就是我们要的答案

高斯消元模板:

 #include<stdio.h>
    #include<algorithm>
    #include<iostream>
    #include<string.h>
    #include<math.h>
    using namespace std;
    const int MAXN=50;
    int a[MAXN][MAXN];//增广矩阵
    int x[MAXN];//解集
    bool free_x[MAXN];//标记是否是不确定的变元
    int gcd(int a,int b){
        if(b == 0) return a; else return gcd(b,a%b);
    }
    inline int lcm(int a,int b){
        return a/gcd(a,b)*b;//先除后乘防溢出
    }
    // 高斯消元法解方程组(Gauss-Jordan elimination).(-2表示有浮点数解,但无整数解,
    //-1表示无解,0表示唯一解,大于0表示无穷解,并返回自由变元的个数)
    //有equ个方程,var个变元。增广矩阵行数为equ,分别为0到equ-1,列数为var+1,分别为0到var.
    int Gauss(int equ,int var){
        int i,j,k;
        int max_r;// 当前这列绝对值最大的行.
        int col;//当前处理的列
        int ta,tb;
        int LCM;
        int temp;
        int free_x_num;
        int free_index;

        for(int i=0;i<=var;i++){
            x[i]=0;
            free_x[i]=true;
        }

        //转换为阶梯阵.
        col=0; // 当前处理的列
        for(k = 0;k < equ && col < var;k++,col++){// 枚举当前处理的行.
        // 找到该col列元素绝对值最大的那行与第k行交换.(为了在除法时减小误差)
            max_r=k;
            for(i=k+1;i<equ;i++){
                if(abs(a[i][col])>abs(a[max_r][col])) max_r=i;
            }
            if(max_r!=k){// 与第k行交换.
                for(j=k;j<var+1;j++) swap(a[k][j],a[max_r][j]);
            }
            if(a[k][col]==0){// 说明该col列第k行以下全是0了,则处理当前行的下一列.
                k--;
                continue;
            }
            for(i=k+1;i<equ;i++){// 枚举要删去的行.
                if(a[i][col]!=0){
                    LCM = lcm(abs(a[i][col]),abs(a[k][col]));
                    ta = LCM/abs(a[i][col]);
                    tb = LCM/abs(a[k][col]);
                    if(a[i][col]*a[k][col]<0)tb=-tb;//异号的情况是相加
                    for(j=col;j<var+1;j++){
                        a[i][j] = a[i][j]*ta-a[k][j]*tb;
                    }
                }
            }
        }
        // 1. 无解的情况: 化简的增广阵中存在(0, 0, ..., a)这样的行(a != 0).
        for (i = k; i < equ; i++){ // 对于无穷解来说,如果要判断哪些是自由变元,那么初等行变换中的交换就会影响,则要记录交换.
            if (a[i][col] != 0) return -1;
        }
        // 2. 无穷解的情况: 在var * (var + 1)的增广阵中出现(0, 0, ..., 0)这样的行,即说明没有形成严格的上三角阵.
        // 且出现的行数即为自由变元的个数.
        if (k < var){
            return var - k; // 自由变元有var - k个.
        }
        // 3. 唯一解的情况: 在var * (var + 1)的增广阵中形成严格的上三角阵.
        // 计算出Xn-1, Xn-2 ... X0.
        for (i = var - 1; i >= 0; i--){
            temp = a[i][var];
            for (j = i + 1; j < var; j++){
                if (a[i][j] != 0) temp -= a[i][j] * x[j];
            }
            if (temp % a[i][i] != 0) return -2; // 说明有浮点数解,但无整数解.
            x[i] = temp / a[i][i];
        }
        return 0;
    }
    int main(void){
    //    freopen("in.txt", "r", stdin);
    //    freopen("out.txt","w",stdout);
        int i, j;
        int equ,var;
        while (scanf("%d %d", &equ, &var) != EOF){
            memset(a, 0, sizeof(a));
            for (i = 0; i < equ; i++){
                for (j = 0; j < var + 1; j++){
                    scanf("%d", &a[i][j]);
                }
            }
            int free_num = Gauss(equ,var);
            if (free_num == -1) printf("无解!\n");
            else if (free_num == -2) printf("有浮点数解,无整数解!\n");
            else if (free_num > 0){
                printf("无穷多解! 自由变元个数为%d\n", free_num);
                for (i = 0; i < var; i++){
                    if (free_x[i]) printf("x%d 是不确定的\n", i + 1);
                    else printf("x%d: %d\n", i + 1, x[i]);
                }
            }else{
                for (i = 0; i < var; i++){
                    printf("x%d: %d\n", i + 1, x[i]);
                }
            }
            printf("\n");
        }
        return 0;
    }

试了几次后,我们得到这个线性方程组

  • 281=a95+b36+c11+d5
  • 18061=a6336+b2245+c781+d281
  • 2245=a781+b281+c95+d36
  • 6336=a2245+b781+c281+d95

将所得的线性方程组代入得到在这里插入图片描述
因此f(n)=f(n-1)+5*f(n-2)+f(n-3)-f(n-4)

有了递推式,很容易写出矩阵快速幂

#include<stdio.h>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
LL n,mod;
struct node{
	LL z[5][5];
}A,B;

node mul(node a,node b)
{
    node ret;
    memset(ret.z,0,sizeof ret.z);
    for(int i=0;i<5;++i)
        for(int j=0;j<5;++j)
        {
            ret.z[i][j]=0;
            for(int k=0;k<5;++k)
                ret.z[i][j]=(ret.z[i][j]+a.z[i][k]*b.z[k][j]%mod+mod)%mod;
        }
    return ret;
}
node Pow(node a,LL n)
{
    node ret;
    memset(ret.z,0,sizeof(ret.z));
    for(int i=0;i<5;++i)    ret.z[i][i]=1;
    node tmp=a;
    while(n)
    {
        if(n&1)ret=mul(ret,tmp);
        tmp=mul(tmp,tmp);
        n>>=1;
    }
    return ret;
}

int main()
{
	mod=1e9+7;
	while(~scanf("%lld",&n)){
		if(n<=5){
			if(n==1)
				printf("1\n");
			else if(n==2)
				printf("5\n");
			else if(n==3)
				printf("11\n");
			else if(n==4)
				printf("36\n");
			else if(n==5)
				printf("95\n");
			continue;
		}
		A.z[0][0]=1;
		A.z[0][1]=5;
		A.z[0][2]=1;
		A.z[0][3]=-1;
		A.z[1][0]=1;
		A.z[2][1]=1;
		A.z[3][2]=1;
		A.z[4][3]=1;
		
		
		B.z[0][0]=95,B.z[1][0]=36,B.z[2][0]=11,B.z[3][0]=5,B.z[4][0]=1;
		node ans = mul(Pow(A,n-5),B);
		printf("%d\n",ans.z[0][0]);
	}
}

猜你喜欢

转载自blog.csdn.net/why932346109/article/details/89194964