bzoj 2004 【hnoi2010】公交线路

时间限制:1秒  内存限制:64M

【问题描述】

  小 Z 所在的城市有 N 个公交车站,排列在一条长为 N-1 公里的直线上,从左到右依次编号为 1 到 N,相邻公交车站间的距离均为 1 公里。

  作为公交车线路的规划者,小 Z 调查了市民的需求,决定按以下规则设计线路:

  1.设共有 K 辆公交车,则 1 到 K 号车站作为始发站, N-K+1 到 N 号车站作为终点站。
  2.每个车站必须被一辆且仅一辆公交车经停(始发站和终点站也算被经停)。
  3.公交车只能从编号较小的车站驶向编号较大的车站。
  4.一辆公交车经停的相邻两个车站间的距离不得超过 P 公里。

  注意“经停”是指经过并停车, 因经过不一定会停车,故经停与经过是两个不同的概念。

  在最终确定线路之前,小 Z 想知道有多少种满足要求的方案。由于答案可能很大,你只需求出答案对 30031 取模的结果。

【输入格式】

  只有一行,其中包含用空格隔开的三个正整数N, K,P, 分别表示公交车站数,公交车数, 一辆公交车经停的相邻两个车站间的最大距离。

【输出格式】

  仅包含一个整数,表示满足要求的方案数对 30031 取模的结果。

【输入样例】

【样例1】
 10 3 3

【样例2】
 5 2 3

【样例3】
 10 2 4

【输出样例】

【样例1】
 1

【样例2】
 3

【样例3】
 81

【样例解释】

  样例一满足要求的方案只有1种,即: (1,4,7,10), (2,5,8), (3,6,9)。
  样例二满足要求的方案有3种,即: (1,3,5), (2,4); (1,3,4), (2,5)和(1,4), (2,3,5)。

【数据范围】

40%的数据满足N≤1000。
100%的数据满足1 < N < 10^9, 1 < P ≤1 0, K < N, 1 < K ≤ P

【来源】

bzoj 2004

一道极其坑的题,开始想了好久都没有思路。

先说一下我看到这道题的第一反应吧:
1.p<=10 一定是状态压缩用的。
2.n<=1*10^9 作为一道递推绝对要用矩阵优化。

然后我就卡死了,推了好久才得出的方程。
d[i][A]:前i个站最后p个站的n辆车最后停的位置为A(停了的2进制位就为1)的方案数。(最后一个站必须有车停)

转移的时候我们强制第一个先走,走一步(不然会重)。这样就可以得出哪些情况之间可以转移了。我们就可以矩阵优化了。

答案就是最后n个站都有车停的情况。

代码如下:

#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int maxn=150;
const int mod=30031;

struct mat
{
    int n,m,v[maxn][maxn];
    mat()
    {
        memset(v,0,sizeof(v));
        m=n=0;
    }
    friend mat operator * (mat a,mat b)
    {
        mat c;
        c.m=a.n;c.n=b.m;
        for(int i=1;i<=c.m;i++)
        for(int j=1;j<=b.m;j++)
        for(int k=1;k<=a.m;k++)
        c.v[i][j]=(c.v[i][j]+a.v[i][k]*b.v[k][j])%mod;
        return c;
    }
}a,B;
int g[maxn],top=0,n,m,p,b[maxn],ans;

int work(int x){
    int t=0;
    while(x)
    {
        t++;
        x-=(x&(-x));
    }
    return t;
}

bool check(int i,int j){
    int x=g[i],y=g[j];
    x=x<<1;
    x-=b[p+1];
    x=x^y;
    return x-(x&(-x))==0;
}

mat qkpow(mat a,int x){
    mat t,c;
    c.m=c.n=a.m;
    for(int i=1;i<=c.m;i++) c.v[i][i]=1;
    t=a;
    while(x)
    {
        if (x&1) c=c*t;
        x=x>>1;
        t=t*t;
    }
    return c;
}

int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    scanf("%d%d%d",&n,&m,&p);
    b[1]=1;for(int i=2;i<=p+1;i++) b[i]=b[i-1]<<1;
    for(int i=b[p];i<=b[p+1]-1;i++)
    if(work(i)==m)
    {
        g[++top]=i;
        if(i==b[p+1]-b[p-m+1])  ans=top;
    }
    a.m=a.n=top;
    for(int i=1;i<=top;i++)
    for(int j=1;j<=top;j++)
    if(check(i,j)) a.v[j][i]=1;
    B.n=top,B.m=1;
    B.v[ans][1]=1;
    B=qkpow(a,n-m)*B;
    printf("%d\n",B.v[ans][1]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值