[ABC213H]Stroll

Stroll

题解

分治 N T T NTT NTT板子。

首先看到 n ⩽ 10 n\leqslant 10 n10这种条件,我们应该很容易想到通过状压或者矩阵对其维护。
这两者明显都是 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=0jdpuk,ipk,ji
很明显的一个可以 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 rl+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;
}

谢谢!!!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值