QYU10742 [ICPC-2025 Inv WHN] 路径求和问题 学习笔记
QYU10742 [ICPC-2025 Inv WHN] 路径求和问题 学习笔记
题意简述
有一个 \(n\times m\) 的网格。坐标为 \((i,j)\) 的格子有一个颜色 \(c_{i,j}\)。定义一条路径的权值为经过格子的颜色种数。求所有从 \((1,1)\) 开始,只往右或往下走,最后走到 \((n,m)\) 的路径的权值总和。
\(n\times m\le 10^5\)。
做法解析
我们显然一种颜色一种颜色的分析。每种颜色 \(i\) 造成的贡献等于:所有经过颜色 \(i\) 格子且满足题意的路径的数量。
这个问题有两种做法。
- 一种是 \(O(nm)\) 的。不需要我教你这个形如 \(dp_{i,j}=dp_{i-1,j}\otimes dp_{i,j-1}\) 的东西怎么做吧。
- 一种是 \(O(k^2)\) 的。其中 \(k\) 为该颜色格子的数量。这与 CFP559C 的做法同理(仅在最后一步有略微不同)。
然后你拿根号分治把它们拼起来就行了。
代码实现
#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
using namespace omodint;
using mint=m998;
const int MaxN=1e5+5,MaxNr=320,MaxK=2e3+5;
mint facr[MaxN*2],finv[MaxN*2];
using namespace omathe;
int N,M,X,Y,cnt[MaxN],klim;
vector<int> C[MaxN];
vector<array<mint,2>> dp1[MaxN];
mint dp2[MaxNr];
struct anob{
int x,y;
friend bool operator<(anob a,anob b){return a.x==b.x?a.y<b.y:a.x<b.x;}
};
vector<anob> A[MaxN];
mint solve1(int cc){
dp1[1][0][0]=1;
for(int i=1;i<=N;i++){
for(int j=1;j<=M;j++){
dp1[i][j][1]=dp1[i-1][j][1]+dp1[i][j-1][1];
mint tmp=dp1[i-1][j][0]+dp1[i][j-1][0];
if(C[i][j]==cc)dp1[i][j][1]+=tmp,dp1[i][j][0]=0;
else dp1[i][j][0]=tmp;
}
}
return dp1[N][M][1];
}
mint solve2(int u){
mint res=0;
sort(A[u].begin(),A[u].end());
for(int i=0;i<cnt[u];i++){
auto [xi,yi]=A[u][i];
dp2[i]=Comb(xi+yi-2,xi-1);
for(int j=0;j<i;j++){
if(A[u][j].y>A[u][i].y)continue;
auto [xj,yj]=A[u][j];
dp2[i]-=dp2[j]*Comb((xi-xj)+(yi-yj),(xi-xj));
}
res+=dp2[i]*Comb((N-xi)+(M-yi),(N-xi));
}
return res;
}
mint ans;
void befinit(int n,int m){
klim=sqrt(n*m);
for(int i=0;i<=n;i++){
C[i].resize(m+1);
dp1[i].resize(m+1);
}
for(int i=1;i<=n*m;i++)A[i].clear();
ans=0,fill(cnt,cnt+n*m+1,0);
}
void mian(){
readis(N,M),befinit(N,M);
for(int i=1;i<=N;i++){
for(int j=1;j<=M;j++){
readi(X),C[i][j]=X;
cnt[X]++,A[X].push_back({i,j});
}
}
for(int i=1;i<=N*M;i++)if(cnt[i])ans+=(cnt[i]>klim?solve1(i):solve2(i));
writil(miti(ans));
}
int Tcn;
int main(){
readi(Tcn),premwork(Ocp5*2);
while(Tcn--)mian();
return 0;
}
浙公网安备 33010602011771号