luogu1357花园

参考题解http://blog.csdn.net/largecub233/article/details/73457992

本题中的m,k比较小,因此可以用来状压,共有n朵花,设到第i朵花时候的状态为f[i][j],j为以i为结束点的的最近m个花的情形。那么当前的j状态可以由哪些k转移到呢,j左移一位后补0或者1。我们可以提前枚举转移的状态,构造一个状态转移图v[j][k]。

那么,if(v[j][k])f[i][j]=(f[i][j]+f[i-1][k])%p;因此我们要n次计算由v[][]主导的转移,类似与跑n次floyed。因此可以用快速幂优化。
而且我们建立v[][]后,实际就是状态之间的转移有向图,我们用0代表不能转移,用1代表可以转移。也就形成了一个01可达矩阵。
在图论里面,这个矩阵的n次幂的意义就是经过n步后可达的方案数,可以说是状压dp,也可以说是图论的题目。
参考代码:

#include<bits/stdc++.h>
#define Ll long long
using namespace std;
struct jv{
    Ll a[64][64];
    jv(){memset(a,0,sizeof a);}
}v,a;
bool ok[64];
int bb[10],b[10];
Ll n,m,k,w,ans,mo=1e9+7;
void cunt(int x){//统计是否有小于k个c 
	int y=x,s=0;
	while(y)s+=y%2,y/=2;
	if(s<=k)ok[x]=1;
//	cout<<x<<" ok"<<ok[x]<<" ; ";	
}
void workv(){
	int x;
	for(int i=0;i<=w;i++)cunt(i);
	for(int i=0;i<=w;i++){
		if(ok[i]<=k){
			int j=i*2%(w+1);
			if(ok[j])v.a[i][j]=1;
			j++; j%=(w+1);
			if(ok[j])v.a[i][j]=1;
		}
	} 	
} 
jv cheng(jv a,jv b){
    jv c;
    for(int i=0;i<=w;i++)
        for(int j=0;j<=w;j++)
            for(int k=0;k<=w;k++)
                c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j])%mo;
    return c;        
}
jv ksm(jv x,Ll y){
    jv ans=x;
    for(y--;y;y>>=1,x=cheng(x,x))
        if(y&1)ans=cheng(ans,x);
    return ans;
}
int main()
{
    scanf("%lld%lld%lld",&n,&m,&k);
    w=(1<<m)-1;
   // dfs(1);
   workv();
    a=ksm(v,n);
    for(int i=0;i<=w;i++)if(ok[i])ans=(ans+a.a[i][i])%mo;
    printf("%lld",ans);
}

不写矩阵幂可以得70分。
 

#include<bits/stdc++.h>
#define Ll long long
using namespace std;
int v[65][65],ok[65];//ok存储当前状态是否可行,v存储当前状态是否可以转移。 
Ll f[100010][165];
Ll n,m,k,w,ans,mo=1e9+7;
void cunt(int x){//统计是否有小于k个c 
	int y=x;
	while(y)ok[x]+=y%2,y/=2;
//	cout<<x<<" ok"<<ok[x]<<" ; ";	
}
void workv(){
	int x;
	for(int i=0;i<w;i++)cunt(i);
	for(int i=0;i<w;i++){
		if(ok[i]<=k){
			int j=i*2%w;
			if(ok[j]<=k)v[i][j]=1;
			j++; j%=w;
			if(ok[j]<=k)v[i][j]=1;
		}
	} 	
} 

void work(int x){
    memset(f,0,sizeof f);
    f[m][x]=1;
    for(int i=m+1;i<=n+m;i++)
        for(int j=0;j<w;j++)
            for(int k=0;k<w;k++)
                if(v[j][k])f[i][j]=(f[i][j]+f[i-1][k])%mo;//进行n次v[j][k]yuan 
    ans=(ans+f[n+m][x])%mo;
}
int main()
{  
    scanf("%lld%lld%lld",&n,&m,&k);
    w=(1<<m);
   
   workv();
    for(int i=0;i<w;i++)if(ok[i]<=k)work(i);
    printf("%lld",ans);
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值