BZOJ (lydsy) 1875 HH去散步 解题报告


HH去散步
Time Limit: 20 Sec
Memory Limit: 64 MB

Description
HH有个一成不变的习惯,喜欢饭后百步走。所谓百步走,就是散步,就是在一定的时间内,走过一定的距离。但是同时HH又是个喜欢变化的人,所以他不会立刻沿着刚刚走来的路走回。又因为HH是个喜欢变化的人,所以他每天走过的路径都不完全一样,他想知道他究竟有多 少种散步的方法。现在给你学校的地图(假设每条路的长度都是一样的都是1),问长度为t,从给定地点A走到给定地点B共有多少条符合条件的路径?
Input
第一行:五个整数NMtAB
N表示学校里的路口的个数
M表示学校里的 路的条数
t表示HH想要散步的距离
A表示散步的出发点
B则表示散步的终点。
接下来M
每行一组AiBi,表示从路口Ai到路口Bi有一条路。
数据保证Ai = Bi,但不保证任意两个路口之间至多只有一条路相连接。 
路口编号从0N -1 
同一行内所有数据均由一个空格隔开,行首行尾没有多余空格。没有多余空行。 
答案模45989
N ≤ 20M ≤ 60t ≤ 2300 ≤ A,B
Output
一行,表示答案。
Sample Input
4 5 3 0 0
0 1
0 2
0 3
2 1
3 2
Sample Output
4
Solution
看到t的取值范围,容易想到此题的解法为矩阵快速幂。
容易想到,我们可以设dpi, j表示从点i到点j的方案数,出初始时,可以令dpi, j表示图中点i到点j的边的数量,由于边是无向的,所以恒有dpi, j=dpj, i。方案数就是dpt i, j
于是,我们很容易地得到了以下程序:
int main()
{
    int n, m, t, s, e;
    matrix dp;
    read(n); read(m); read(t); read(s); read(e);
    for (int i = 0; i<m; i++)
    {
        int from, to;
        read(from); read(to);
        dp.data[from][to]++;
        dp.data[to][from]++;
    }
    dp = pow(dp, t);
    cout << dp.data[s][e] << endl;
    return 0;
}
读者不要急于复制,我们忽略了一句十分重要的话“他不会立刻沿着刚刚走来的路走回”。采用如上程序得出的方案数比答案多。

为了解决这一问题,我们容易想到为dp加一个维度,或者改变一个维度的意义,比如,我们设dpk, i, j表示在k时刻,到达点i且上一次经过的边为j的方案数。转移过程比较明确,实现如下:
for (int k = 1; k<t; k++)
{
    for (int i = 0; i<n; i++)
    {
        for (int j = graph[i].nex; j; j = graph[j].nex)
        {
            for (int l = 0; l<m; l++)
            {
                if (l == graph[j].num)continue;
                dp[k + 1][graph[j].to][graph[j].num] += dp[k][i][l];
            }
            dp[k + 1][graph[j].to][graph[j].num] %= mod;
        }
    }
}
首先解释各个变量的含义:graph——使用邻接表储存的图,graph[i].num表示边的编号,从零开始,正向边和反向边的编号相同。可如此以来,dp多了一个可以防止走回头路的维度,但少了一个区分起点的维度。似乎无法用矩阵快速幂优化。
这时,我们转换思路
我们设dpi, j表示从第i条边到第j条边的方案总数,为了方便合并,我们假设计数时不考虑初始边。这样便又可以“生搬硬套”矩阵快速幂了。可是,问题来了,对于下图(即样例):
初始时,dp10, 3=1(为了区分不同阶段的状态,我们用指数表示经过的边的数量,不包括起始边),dp13, 0=1。用矩阵乘法计算dp20, 0。发现算出的答案是3,显然这包括了走回头路的情况,原因就是我们无法通过状态确定开始移动时的方向,即在边0上,究竟是通过点1到达了边3,还是通过点02到达了边3?这样还是没有解决走回头路的问题。我们可以考虑在加一个维度,但这样岂不是又走回了方法二的老路?
其实,区分方向并不复杂,仅需将正向边和反向边用不同的编号标识,如果边的编号从0开始,正向边、反向边的编号相邻,可以用按位异或运算快速得到反向边的编号。
Code
//dp[i][j]^(k-1)——从边i->j,共经过k条边(不包括起点)的方案数
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
const unsigned int mod = 45989;

void read(int &x)
{
    x = 0; char ch = getchar(); bool f = 0;
    while (ch>'9' || ch<'0') { if (ch == '-')f = 1; ch = getchar(); }
    while (ch >= '0'&&ch <= '9') { x = x * 10 + ch - 48; ch = getchar(); }
    if (f)x = -x;
}

struct matrix
{
    unsigned int data[120][120];
    matrix operator*(const matrix &b)const
    {
        matrix c;
        for (int i = 0; i<120; i++)
        {
            for (int j = 0; j<120; j++)
            {
                c.data[i][j] = 0;
                for (int k = 0; k<120; k++)
                {
                   c.data[i][j] += data[i][k] * b.data[k][j];
                   c.data[i][j] %= mod;
                }
            }
        }
        return c;
    }
};

matrix pow(matrix a, int b)
{
    matrix m = a, ans;
    memset(ans.data, 0, sizeof(ans.data));
    for (int i = 0; i<120; i++)
    {
        ans.data[i][i] = 1;
    }
    while (b)
    {
        if (b & 1)ans = ans*m;
        m = m*m;
        b >>= 1;
    }
    return ans;
}
matrix dp;

struct graph_t
{
    int to, nex, num;
}graph[181];
int tail = 20;
int times = -1;

void addedge(int u, int v)
{
    int tmp1 = graph[u].nex, tmp2 = graph[v].nex;
    graph[u].nex = ++tail, graph[v].nex = ++tail;
    graph[tail - 1].to = v, graph[tail].to = u;
    graph[tail - 1].num = ++times, graph[tail].num = ++times;
    graph[tail - 1].nex = tmp1, graph[tail].nex = tmp2;
}

int main()
{
    int n, m, t, s, e;
    read(n); read(m); read(t); read(s); read(e);
    for (int i = 0; i<m; i++)
    {
        int from, to;
        read(from); read(to);
        addedge(from, to);
    }
    for (int i = 0; i<n; i++)
    {
        for (int j = graph[i].nex; j; j = graph[j].nex)
        {
            for (int k = graph[i].nex; k; k = graph[k].nex)
            {
                if (graph[j].num != graph[k].num)
                {
                   dp.data[graph[j].num ^ 1][graph[k].num]++;
                }
            }
        }
    }
    dp = pow(dp, t - 1);
    unsigned int ans = 0;
    for (int i = graph[s].nex; i; i = graph[i].nex)
    {
        for (int j = graph[e].nex; j; j = graph[j].nex)
        {
            ans += dp.data[graph[i].num][graph[j].num ^ 1];
            ans %= mod;
        }
    }
    cout << ans << endl;
    return 0;
}

没有评论:

发表评论