[BZOJ 2004][HNOI 2010]Bus 公交线路(矩阵快速幂加速DP)

142 篇文章 0 订阅
98 篇文章 0 订阅

题目链接

http://www.lydsy.com/JudgeOnline/problem.php?id=2004

思路

f[i][S]i,S(Sk1k) ,很容易推出DP的方程 f[i][S]=i1j=kf[j][S],SS
然后注意到题目中有限制: p ,这个条件非常重要,可以大大减少 S 的范围——从[1,2n1]缩小到 [1,2p1] ,那么空间上就吃得消了。
然后发现对于每个 f[i][S] 来说,可以拿来转移到它的(不考虑是否合法)的 S 都是一样的,那么就可以用矩阵快速幂来优化这个DP。很显然这个DP的状态转移过程是个DAG,如下图
这里写图片描述
这样就可以把这个DP的过程看成是在每个节点代表一个状态的DAG里,用 nk 单位时间从初始状态走到终点状态的方案数,这是个非常经典的问题,在《工程数学线性代数》课本中就有相应的例题。
最终的状态应该如下图所述,这个很显然
这里写图片描述
然后就是判断 SS 的问题了, S 中的所有1都向左移动一格,保留后p位,个位用 0 补齐,这时候的S S 只有一位不同就是合法的了,这个在草稿纸中画一画就能明白。
我很SB地因为模数习惯性取1e9+7贡献了一发WA,郁闷

代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 140
#define MOD 30031

using namespace std;

struct Matrix
{
    int num[MAXN][MAXN];
    int n,m; //n*m大小矩阵
    void setOne(int a,int b)
    {
        n=a,m=b;
        for(int i=1;i<=min(n,m);i++) num[i][i]=1;
    }
    Matrix() { memset(num,0,sizeof(num)); }
}T,A,one;

Matrix operator*(Matrix a,Matrix b)
{
    Matrix c;
    c.n=a.n,c.m=b.m;
    for(int i=1;i<=c.n;i++)
        for(int j=1;j<=c.m;j++)
            for(int k=1;k<=a.m;k++)
                c.num[i][j]=(c.num[i][j]+a.num[i][k]*b.num[k][j])%MOD;
    return c;
}

Matrix fastPow(Matrix base,int pow)
{
    Matrix ans;
    ans.setOne(base.n,base.m);
    while(pow)
    {
        if(pow&1) ans=ans*base;
        base=base*base;
        pow>>=1;
    }
    return ans;
}

int calc(int x) //计算x的二进制中1的个数
{
    int sum=0;
    while(x)
    {
        sum++;
        x-=x&(-x); //x去掉最后一个1
    }
    return sum;
}

int n,k,p,goal; //goal是目标状态

bool canConvert(int to,int from) //检查状态from能否一步转移到状态to
{
    from=(from-(1<<(p-1)))<<1; //这一步相当于把from向左推了一位,个位用0补齐
    int tmp=from^to; //tmp应该只有一个1
    if(tmp==(tmp&(-tmp))) return true; //tmp只有一个1,则是合法的
    return false; //否则是不合法的
}

int status[MAXN],top=0; //保存所有DP过程中可能出现的状态的栈

int main()
{
    scanf("%d%d%d",&n,&k,&p);
    for(int S=(1<<(p-1));S<(1<<p);S++) //枚举DP状态S,S是合法状态当且仅当S的二进制中1的个数恰好为k
    {
        if(calc(S)==k)
        {
            status[++top]=S;
            if(S==(1<<p)-1-((1<<(p-k))-1)) goal=top; //S是最终要达到的状态
        }
    }
    for(int i=1;i<=top;i++)
        for(int j=1;j<=top;j++)
            if(canConvert(status[i],status[j]))
                T.num[i][j]=1;
    A.n=A.m=T.n=T.m=top;
    A.num[1][goal]=1;
    T=fastPow(T,n-k);
    A=A*T;
    printf("%d\n",A.num[1][goal]);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值