洛谷P8502题解

[problem] \color{blue}{\texttt{[problem]}} [problem]

在这里插入图片描述

[Solution] \color{blue}{\texttt{[Solution]}} [Solution]

这题最恶心的地方是卡空间

我们先考虑不卡空间时怎么做。

直接并不好做,我们考虑正难则反,即利用容斥原理。答案应为从 a a a 没有任何限制经过 m m m 条边到 b b b 的方案数减去从 a a a 经过 m m m 条边到 b b b 且必须经过 c c c 的方案数。

自然地,我们会想到用 f m , i , j f_{m,i,j} fm,i,j 表示从 i i i 经过 m m m j j j 的方案数。显然其初始条件 f 0 , i , i = 1 f_{0,i,i}=1 f0,i,i=1

不难得到, f f f 的转移方程为:

f m , i , j = ∑ k → j f m − 1 , i , k f_{m,i,j}=\sum\limits_{k \to j}f_{m-1,i,k} fm,i,j=kjfm1,i,k

其中 k → j k \to j kj 表示节点 k k k 可以到达节点 j j j

很显然,由于每个节点可以到达的节点是一段连续的区间,我们可以用差分来优化我们的 dp \text{dp} dp

表面上看,最终答案似乎就是

f m , a , b − ∑ k = 0 m ( f k , a , c × f m − k , c , b ) f_{m,a,b}-\sum\limits_{k=0}^{m} \left (f_{k,a,c} \times f_{m-k,c,b} \right ) fm,a,bk=0m(fk,a,c×fmk,c,b)

但是,这样的计数是错误的。因为我们可以多次经过 c c c 这个节点,而每一次经过节点 c c c 都会被重复计数。比如在一条途径中,我们在 3 , 7 , 9 3,7,9 3,7,9 次经过节点 c c c,那么我们在 k = 3 , 7 , 9 k=3,7,9 k=3,7,9 时都会把这条途径计入。

怎样避免重复?我们可以强制从 c c c b b b 的路径不能再经过 c c c。那这样会不会导致计数遗漏?不会,因为从 a a a c c c 的路径还是可以重复经过 c c c 的。

于是,我们可以新开一个数组 g m , i , j g_{m,i,j} gm,i,j 表示从 i i i 经过 m m m 条边到 j j j 但不能经过 i i i 的方案数。 g g g 的转移和 f f f 相似,只是 g l , i , i = 0 ( l = 1 , 2 , … , m ) g_{l,i,i}=0(l=1,2,\dots,m) gl,i,i=0(l=1,2,,m)

但是这题卡空间。

我们发现, f f f g g g m m m 时的转移都只和 ( m − 1 ) (m-1) (m1) 有关。于是 f f f g g g 完全可以重复利用,只需要开二维即可。这样是可以满足空间要求的。但是有个问题,就是我们不能随时询问随时回答,因为我们并没有保留任意的 m m m 的状态信息。

怎么办?不能在线回答询问,那就离线!于是主体思路完成。

离线这一点,这题的难度上了一个台阶。

Code \color{blue}{\text{Code}} Code

const int mod=998244353;
inline void add(int &a,int b){
	a=(a+b>mod?a+b-mod:a+b);
}

const int N=510,M=105;
int l[N],r[N],n,q,m;
int f[2][N][N],g[2][N][N];

struct Query{
	int a,b,c,m,ans,f[M],g[M];
}Q[100010];

int main(){
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++)
		scanf("%d%d",&l[i],&r[i]);
	for(int i=1;i<=q;i++){
		scanf("%d%d",&Q[i].a,&Q[i].b);
		scanf("%d%d",&Q[i].c,&Q[i].m);
		m=max(m,Q[i].m);
	}
	
	for(int i=1;i<=n;i++)
		f[0][i][i]=g[0][i][i]=1;//init
	
	for(int k=0;k<=m;k++){
		bool s=(k&1),t=(s^1);//s 表示这一维的信息,t 表示下一维
		
		for(int i=1;i<=q;i++){
			int a=Q[i].a,b=Q[i].b,c=Q[i].c;
			Q[i].f[k]=f[s][a][c];Q[i].g[k]=g[s][c][b];
			if (k==Q[i].m) Q[i].ans=f[s][a][b];
		}
		
		memset(f[t],0,sizeof(f[t]));
		memset(g[t],0,sizeof(g[t]));
		
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				if (r[j]){//可转移 
					add(f[t][i][l[j]],f[s][i][j]);
					add(f[t][i][r[j]+1],mod-f[s][i][j]);
					add(g[t][i][l[j]],g[s][i][j]);
					add(g[t][i][r[j]+1],mod-g[s][i][j]);
				}
		
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				add(f[t][i][j],f[t][i][j-1]);
				add(g[t][i][j],g[t][i][j-1]);
			}
			g[t][i][i]=0;
		}
	}
	
	for(int i=1;i<=q;i++)
		for(int j=0;j<=Q[i].m;j++)
			Q[i].ans=(Q[i].ans-1ll*Q[i].f[j]*Q[i].g[Q[i].m-j]%mod)%mod;
	
	for(int i=1;i<=q;i++){
		Q[i].ans=(Q[i].ans+mod)%mod;
		printf("%d\n",Q[i].ans);
	}
	
	return 0;
}
  • 15
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值