DTOI 3523 环线(tour)

来自 FJWC 2018 Day5

以下为题解

对于 30% 的数据, n<=4 0,k<=1000 。计算每个点在第 1 – k-1步到每个点 步到每个点 的方法数 ,将所有自环(回到己)的方案统计起来。 O(n^3*k)
对于 60% 的数据, n<=80 ,k<=10^6 。
计算每个点在第 1 – k-1步到每个点的方法数 ,使用 ,使用 矩阵乘法快速幂 解决。
A[i][j]=1 表示 i,j 之间有无向边 。
设 B矩阵中仅 B[i][i]=1 ,其他为 0(单位矩阵) 。
令 B * A^x = C B * A^x = C B * A^x = C B * A^x = C B * A^x = C,则 C[i][j] 表示 i到 j长度恰为 x的路径数 。将 B*A ,B*A^2 , B*A^3 …… B*A^(k -1) 的主对角线值 (C[i][i ],即自环 )累计即为答案 。
我们可以新建 n个虚拟点 ,不妨 标号为 为 n+0 ,n+1, ..n+ (n-1) 。从点 i连一 条到点 i+n 的边 (i < n ),再给所有的点 n+i 连一条向自己的边。 连一条向自己的边。 这一点 用矩阵 乘法中体现的话 ,即将矩阵扩大为 2n*2n :
A[i,i+n]=1 , n,n] = 1 ( i< n)
令 B * A^k = C B * A^k = C B * A^k = C B * A^k = CB * A^k = CB * A^k = C,则ΣC[i,i+n]C[i,i+n] (i< n) - n 即为答案 即为答案 。即从点 i经过 n步走 到点 i+n 的方案数,减去长度为 0的自环方案数 (n)
时间复杂度 O(logk*(2n)^3)
对于 100% 的数据, n<=100 ,k<=10^6
计算每个点在第 1 – k-1步到每个点的方法数 。
将 B*A ,B*A^2 ,B*A^3 …… B*A^(k -1) 的主对角线值 (C[i][i] ,即自环 )累 计即为答案 。
相当于 D=B * (A + A^2 3 … A^(k -1)) ,求 D的主对角线 (D[i][i]) (D[i][i]) 的 和。
而求解 A + A^2 3 … A^(k -1) ,类似于 求解 等比数列 。
根据 K的奇偶 有,
如果 k为偶数,那么
(A+A^2+….K) = A+…+K/2)+2*(2)
如果 k为奇数,那么
(A+A^2+….K) = A+…+K/2)+2*(k
时间复杂度 O(logk * n^3)

#include<bits/stdc++.h>
using namespace std;
const int MAXN=106;
typedef long long ll;
int n,k,mod,last,lastc,now,nowc,p;
char ch[MAXN][MAXN];
ll ansn,a[20][MAXN][MAXN],b[2][MAXN],c[2][MAXN];
inline void Multi(ll _a[MAXN][MAXN],ll _b[MAXN][MAXN],ll _c[MAXN][MAXN]) {
    for (int i=1;i<=n;i++)
        for (int k=1;k<=n;k++)
            if (_a[i][k]) for (int j=1;j<=n;j++) 
                if (_b[k][j]) _c[i][j]=(_c[i][j]+_a[i][k]*_b[k][j])%mod;
}
inline void Init(int x) {
    last=lastc=0,now=nowc=1;
    for (int i=1;i<=n;i++) b[0][i]=c[0][i]=0;
    b[0][x]=c[0][x]=1;
}
void fastpow(int x) {
    p=0;
    while (x) {
        if (x&1) {
            for (int i=1;i<=n;i++) {
                c[nowc][i]=0;
                for (int j=1;j<=n;j++) c[nowc][i]=(c[nowc][i]+a[p][i][j]*c[lastc][j])%mod;
                c[nowc][i]+=b[last][i];
                if (c[nowc][i]>=mod) c[nowc][i]-=mod;
            }
            swap(lastc,nowc);
        }
        for (int i=1;i<=n;i++) {
            b[now][i]=0;
            for (int j=1;j<=n;j++) b[now][i]=(b[now][i]+a[p][i][j]*b[last][j])%mod;
            b[now][i]+=b[last][i];
            if (b[now][i]>=mod) b[now][i]-=mod;
        }
        swap(last,now);
        p++;
        x>>=1;
    }
}
int main() {
    scanf ("%d",&n);
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++) scanf (" %c",&ch[i][j]),a[0][i][j]=ch[i][j]=='Y'?1ll:0ll;
    for (int i=1;i<=n;i++) b[i][i]=1ll;
    scanf ("%d%d",&k,&mod);
    for (int i=1;i<20 && (1<<i)<=k;i++) {
        Multi(a[i-1],a[i-1],a[i]);
        for (int j = 1;j <= n;j++,puts(""))
            for (int k = 1;k <= n;k++) printf ("%d ",a[i][j][k]);
            puts("_-----------");
    }
    ansn-=(ll)n;
    for (int i=1;i<=n;i++) {
        Init(i);
        fastpow(k-1);
        ansn+=c[lastc][i];
        ansn%=mod;
    }
    printf ("%lld\n",ansn);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值