Luogu 1357:花园 (DP+矩乘加速)

Luogu 1357:花园

题目传送门
PS:刷刷水题,娱乐身心

【问题描述】

  有n个花圃,形成一个环。每个花圃可以放两种花(A和B)。规定相邻m个花圃里的B花数量不超过k。
  求方案总数(%1e9+7)
  好像旋转后相同也记作不同。。。(应该是固定视角的)
  2<=n<=10^15
  2<=m<=5
  1<=k

【解题思路】

  m<=5,2^5=32。
  显然的状压DP。
  只有选B有影响。
  m个之中的第i个花圃为B,记作2^(i-1)
   f[i][(S>>1)+iAB]+=f[i1][S]; f [ i ] [ ( S >> 1 ) + 当 前 第 i 个 选 A 或 选 B ] + = f [ i − 1 ] [ S ] ;
  —————————————————————
  发现n太大。
  需要用到矩阵乘法加速。
1 2 3  11 12 13
   * 21 22 23 =1*11+2*21+3*31 1*12+2*22+3*32 3*13+2*23+3*33 
     31 32 33
  也就是说,f[i][j]的转移规则在于矩阵G的第j列。
   f[i][j]=nx=1f[i1][x]G[x][j] f [ i ] [ j ] = ∑ x = 1 n f [ i − 1 ] [ x ] ∗ G [ x ] [ j ]
  把所有的转移x->y,设为G[x][y]=1
  —————————————————————
  那初始状态,可以枚举前m个花圃的状态为S。因为是一个环(破环为链),那么末尾n+1~n+m-1也必定状态为S。
  即状态S,经过n次转移,最后状态也是S。

ANS=S=02m1(f[0][S]=1,f[n][S]) A N S = ∑ S = 0 2 m − 1 ( 初 始 f [ 0 ] [ S ] = 1 , 答 案 为 f [ n ] [ S ] )

【代码】

#include<bits/stdc++.h>

#define RE register

#define imax(a,b) ((a>b)?(a):(b))
#define imin(a,b) ((a<b)?(a):(b))

using namespace std;

typedef long long ll;

const int N=40;
const ll mods=1000000007;
ll n,m,k,ans;
int wn;
ll A[N][N],B[N][N],C[N][N],S[N],Q[N];
bool ok[N];

void read(ll &x)
{
    x=0; RE char ch=getchar(); RE ll f=1;
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
    for(; isdigit(ch);ch=getchar()) x=(x<<3)+(x<<1)+ch-'0';
    x*=f;
}

void pre()
{
    for(RE int i=0;i<wn;i++)
    {
        RE int sk=0;
        for(RE int j=0;j<m;j++)
        if((1<<j)&i) sk++;
        if(sk>k) continue;
        ok[i]=1;
        RE int yu=i;
        if(1&i) sk--;
        if(sk<k) { yu=(i>>1)+(1<<(m-1)); A[yu][i]=1; }
        yu=(i>>1); A[yu][i]=1;      
    }
}

void mul()
{
    memset(C,0,sizeof(C));
    for(RE int i=0;i<wn;i++)
    for(RE int j=0;j<wn;j++)
    for(RE int g=0;g<wn;g++) (C[i][j]+=B[i][g]*B[g][j]%mods)%=mods;
    memcpy(B,C,sizeof(C));
}

void kmul()
{
    memset(Q,0,sizeof(Q));
    for(RE int i=0;i<wn;i++)
    for(RE int j=0;j<wn;j++) (Q[j]+=S[i]*B[i][j]%mods)%=mods;
    memcpy(S,Q,sizeof(Q));
}

ll find(ll n,int s)
{
    memset(S,0,sizeof(S));
    S[s]=1; memcpy(B,A,sizeof(A));
    for(;n;n>>=1,mul())
    if(n&1) kmul();
    return S[s];
}

int main()
{
    read(n); read(m); read(k);
    wn=(1<<m);
    pre(); ans=0;
    for(RE int i=0;i<wn;i++) if(ok[i]) (ans+=find(n,i))%=mods;
    printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值