Stroll
题解
分治
N
T
T
NTT
NTT板子。
首先看到
n
⩽
10
n\leqslant 10
n⩽10这种条件,我们应该很容易想到通过状压或者矩阵对其维护。
这两者明显都是
d
p
dp
dp,而该题给的条件我们也可以很快想出一个
d
p
dp
dp思路。
我们定义
d
p
i
,
j
dp_{i,j}
dpi,j表示花费时间
j
j
j,到达点
i
i
i的路径方案数。
但如果直接暴力转移的话时间复杂度达到了可怕的
O
(
m
T
2
)
O\left(mT^2\right)
O(mT2),大概只有铜暴力可以跑过去,果断考虑如何对其进行优化。
我们注意转移式,
d
p
v
k
,
i
=
∑
i
=
0
j
d
p
u
k
,
i
p
k
,
j
−
i
dp_{v_{k},i}=\sum_{i=0}^{j}dp_{u_{k},i}p_{k,j-i}
dpvk,i=i=0∑jdpuk,ipk,j−i
很明显的一个可以
N
T
T
NTT
NTT优化的式子,但显然是不能直接
N
T
T
NTT
NTT的,由于存在反复横跳这样的情况,即不断在两个点之间以
1
1
1的代价走来走去,导致我们直接
N
T
T
NTT
NTT复杂度会变成可怕的
O
(
m
T
2
l
o
g
T
)
O\left(mT^2log\,T\right)
O(mT2logT)。
但这并不意味着我们不能
N
T
T
NTT
NTT,我们可以采用分治
N
T
T
NTT
NTT的思路,看对时间进行分治。
当我们在处理区间
[
l
,
r
]
[l,r]
[l,r]时,就考虑将所有点的
[
l
,
m
i
d
]
[l,mid]
[l,mid]的状态转移到
[
m
i
d
+
1
,
r
]
[mid+1,r]
[mid+1,r],在这种情况下,反复很跳这样的只会在
m
i
d
mid
mid的两侧存在,正确性很容易明确。
对于区间
[
l
,
r
]
[l,r]
[l,r]我们相当于要处理
2
m
2m
2m次两个长度为
r
−
l
+
1
r-l+1
r−l+1的多项式相乘,就像普通分治一样,先处理
[
l
,
m
i
d
]
[l,mid]
[l,mid]内部,再处理
[
l
,
m
i
d
]
[l,mid]
[l,mid]到
[
m
i
d
+
1
,
r
]
[mid+1,r]
[mid+1,r],最后处理
[
m
i
d
+
1
,
r
]
[mid+1,r]
[mid+1,r]内部,保证处理后面的区间时前面已经处理完了。
答案就是
F
1
[
x
T
]
F_{1}[x^T]
F1[xT]
最后时间复杂度为 O ( m T l o g 2 T ) O\left(mTlog^2\,T\right) O(mTlog2T),完全可以过。
源码
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
#define MAXN 1000005
#define lowbit(x) (x&-x)
#define reg register
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
typedef long long LL;
typedef unsigned long long uLL;
const int INF=0x3f3f3f3f;
const int mo=998244353;
const int inv2=499122177;
const int jzm=2333;
const int lim=15;
const int orG=3,invG=332748118;
const double Pi=acos(-1.0);
const double eps=1e-7;
typedef pair<int,int> pii;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
_T f=1;x=0;char s=getchar();
while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
x*=f;
}
template<typename _T>
void print(_T x){if(x<0){x=(~x)+1;putchar('-');}if(x>9)print(x/10);putchar(x%10+'0');}
LL gcd(LL a,LL b){return !b?a:gcd(b,a%b);}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1LL)t=1ll*a*t%p;a=1ll*a*a%p;s>>=1LL;}return t;}
int n,m,T,F[11][MAXN],G[11][MAXN],a[MAXN],b[MAXN],c[MAXN],rev[MAXN];
struct ming{int u,v;}s[MAXN];
void NTT(const int lim,int *A,const int typ){
for(int i=0;i<lim;i++)if(i<rev[i])swap(A[i],A[rev[i]]);
for(int k=1;k<lim;k<<=1){
const int W=qkpow(typ^1?invG:orG,(mo^1)/(k<<1),mo);
for(int i=0;i<lim;i+=(k<<1))
for(int j=i,Wn=1;j<i+k;j++,Wn=1ll*Wn*W%mo){
const int x=A[j],y=1ll*Wn*A[j+k]%mo;
A[j]=add(x,y,mo);A[j+k]=add(x,mo-y,mo);
}
}
if(typ^-1)return ;const int iv=qkpow(lim,mo-2,mo);
for(int i=0;i<lim;i++)A[i]=1ll*A[i]*iv%mo;
}
void sakura(const int l,const int r){
if(l==r)return ;const int mid=l+r>>1;
sakura(l,mid);
for(int i=1;i<=m;i++){
const int u=s[i].u,v=s[i].v;int L=0,lim=1;while(lim<r-l+mid-l+1)L++,lim<<=1;
for(int i=1;i<lim;i++)rev[i]=(rev[i>>1]>>1)|((i&1)<<L-1);
for(int j=l;j<=mid;j++)a[j-l]=F[u][j];
for(int j=1;j<=r-l;j++)b[j]=G[i][j];NTT(lim,a,1);NTT(lim,b,1);
for(int j=0;j<lim;j++)c[j]=1ll*a[j]*b[j]%mo;NTT(lim,c,-1);
for(int j=mid-l+1;j<=r-l;j++)F[v][j+l]=add(F[v][j+l],c[j],mo);
for(int j=0;j<lim;j++)a[j]=c[j]=0;
for(int j=l;j<=mid;j++)a[j-l]=F[v][j];NTT(lim,a,1);
for(int j=0;j<lim;j++)c[j]=1ll*a[j]*b[j]%mo;NTT(lim,c,-1);
for(int j=mid-l+1;j<=r-l;j++)F[u][j+l]=add(F[u][j+l],c[j],mo);
for(int j=0;j<lim;j++)a[j]=b[j]=c[j]=0;
}
sakura(mid+1,r);
}
signed main(){
read(n);read(m);read(T);
for(int i=1;i<=m;i++){read(s[i].u);read(s[i].v);for(int j=1;j<=T;j++)read(G[i][j]);}
F[1][0]=1;sakura(0,T);printf("%d\n",F[1][T]);
return 0;
}