题解:AT_ndpc2026_h コイン拾い

· · 题解

题意概述:

正难则反,考虑对于每个格子 $\left(i,j\right)$,对硬币数的贡献是多少,即求处于 $\left(i,j\right)$ 时,可能有多少枚硬币。 手搓一下样例,发现一件神奇的事:每个格子对硬币数的贡献是一段连续的区间! 具体来说,设从 $\left(1,1\right)$ 到 $\left(i,j\right)$ 可以拿到的最少硬币数为 $a$,最大硬币数为 $b$,则区间 $\left[a,b\right]$ 内的硬币数都是可能的。 :::info[证明] 考虑有公共边或公共点的格子: - 从 $\left(i,j\right)$ 到 $\left(i,j+1\right)$ 有唯一路径(右),二者对硬币数的贡献差最多为 $1$。 - 从 $\left(i,j\right)$ 到 $\left(i+1,j\right)$ 有唯一路径(下),二者对硬币数的贡献差最多为 $1$。 - 从 $\left(i,j\right)$ 到 $\left(i+1,j+1\right)$ 的路径有两条(右下、下右)。由于 $\left(i,j+1\right)$ 和 $\left(i+1,j\right)$ 这两个格子上要么都没有硬币,要么一个有一个没有,要么都有,所以这两个格子上的硬币数之差最多为 $1$,所以 $\left(i,j\right)$ 对硬币数的贡献,扩展到 $\left(i+1,j+1\right)$ 对硬币数的贡献,其差最多为 $1$。 考虑其他格子,必然由以上情况组合得到。得证。 ::: 因此,只需要维护从 $\left(1,1\right)$ 到 $\left(i,j\right)$ 可以拿到的最少硬币数和最大硬币数,分别为 $a,b$,然后区间加 $1$,单点查询。 等等,这不是 ~~线段树~~ 差分吗? 设差分数组为 $d$,每次将 $d_{a}$ 加 $1$,将 $d_{b+1}$ 减 $1$,最终对于每个 $x$ 的答案即为 $\sum\limits_{i=1}^x d_i$。 代码如下: ```c++ const int N=4005; int n,d[2*N],minc[N][N],maxc[N][N]; char s[N][N]; int main() { n=read(); for(int i=1;i<=n;i++) scanf("%s",s[i]+1); minc[1][1]=maxc[1][1]=0; for(int i=2;i<=n;i++) minc[1][i]=maxc[1][i]=minc[1][i-1]+(s[1][i]=='@'); for(int i=2;i<=n;i++) minc[i][1]=maxc[i][1]=minc[i-1][1]+(s[i][1]=='@'); for(int i=2;i<=n;i++) for(int j=2;j<=n;j++) { minc[i][j]=min(minc[i-1][j],minc[i][j-1])+(s[i][j]=='@'); maxc[i][j]=max(maxc[i-1][j],maxc[i][j-1])+(s[i][j]=='@'); } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) d[minc[i][j]]++,d[maxc[i][j]+1]--; print(d[0]),putchar('\n'); for(int i=1;i<=2*n-2;i++) d[i]+=d[i-1],print(d[i]),putchar('\n'); return 0; } ```