杨图计数 + 对角线性质 + 轮廓线计数 + 差分:ICPC2025沈阳H
Cute Young Diagram Counting - Problem - QOJ.ac
我们首先思考一下,翻转操作中,什么是不变的。
假设一个格子在 (i,j),我们让其贡献到 ti+j 里。不难证明,t 序列是始终不变的。
t 序列的形式必然是:0,1,x,y,⋯,z,0,0,⋯
接下来我们证明,所有满足此 t 序列的杨图必然是可以构造出来的。
首先,所有操作都是可逆的,因此我们只需要证明所有 t 序列相同的杨图可以通过某种方式调整到唯一一种杨图即可。
对于一个固定的 t 序列,我们贪心使第一行越长越好,然后递归进入第二行,变成归纳了。
对于一个固定的杨图,我们也是逐行贪心,使第一行越长越好。因此两种方法构造出来的最大杨图是唯一的,得证。
接下来就变成计数问题了,对于一个固定的 t 序列,如何统计有多少种合法的方案。
我们采用一种转化,把此计数问题改成轮廓线计数问题。
我们先把杨图的左边界和上边界无限延长
我们可以得到一些性质:
-
轮廓线必然是每个格子的外围
-
只有 t 相邻的格子其轮廓线才可能相邻
这个性质非常有用,它启示我们,我们只需要对相邻的轮廓线考虑即可。我
更进一步,我们只需要考虑相邻两种轮廓线之间的排列方式!
-
对于每个 ti 的轮廓线数量,其实就是 2(ti−ti+1+1)。
因此我们可以维护 ti 的差分序列,di=ti−ti+1
接下来我们考虑两个相邻 t 轮廓线的组合计数,我们取反对角线,如下图所示:

(图中的 t 理论上要整体+1)
其中5的的外边界是绿色,6的外边界是黄色。我们还能继续瞪出很多性质:
- 最外围的两段肯定是黄色
- 中间肯定是连续两段黄色,连续两段绿色的随便排布。而这种排布必然合法且双射。
如果我们把两段视为一块,那么中间就有 di−1+1 块绿色,di 块黄色,它们可以随便乱排,因此方案数为 (didi−1+di+1)
由于相邻块之间独立,所以答案为 ∏i(didi−1+di+1)
每次加入1行,只会导致4个 d 发生变化,简单维护一下即可。
时间复杂度 O(n)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| #include<bits/stdc++.h> #include<cassert> using namespace std;bool Mbe; #ifdef LOCAL #define debug(...) fprintf(stdout, ##__VA_ARGS__) #else #define debug(...) void(0) #endif #define int long long inline int read(){int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+ (x<<3)+(ch^48);ch=getchar();}return x*f;} #define Z(x) (x)*(x) #define pb push_back #define fi first #define se second #define mo 998244353 #define N 2000010 int pw(int a, int b) { int ans = 1; while(b) { if(b & 1) ans *= a; a *= a; b >>= 1; ans %= mo; a %= mo; } return ans; } int pw(int a) { return pw(a, mo - 2); } int fac[N], inv[N], ifac[N]; void init(int n) { int i; for(i = fac[0] = 1; i <= n; ++i) fac[i] = fac[i-1] * i % mo; ifac[n] = pw(fac[n], mo-2); for(i = n - 1; i >= 0; --i) ifac[i] = ifac[i+1] * (i+1) % mo; for(i = 1; i <= n; ++i) inv[i] = ifac[i] * fac[i-1] % mo; } int C(int n, int m) { if(m > n) return 0; return fac[n] * ifac[m] % mo * ifac[n - m] % mo; } int iC(int n, int m) { if(m > n) return 0; return ifac[n] * fac[m] % mo * fac[n - m] % mo; } inline void Mod(int &a) { if(a >= mo || a <= -mo) a %= mo; if(a < 0) a += mo; } inline void Add(int &a, int b) { a += b; Mod(a); } inline void Mul(int &a, int b) { Mod(b); a *= b; Mod(a); } const int iv2 = pw(2, mo - 2); int n, m, i, j, k, T; int ans, a[N], d[N];
void calc(int i, int o) { if(d[i] == -1 || d[i + 1] == -1) return ; if(o == -1) Mul(ans, iC(d[i] + d[i + 1] + 1, d[i + 1])); if(o == 1) Mul(ans, C(d[i] + d[i + 1] + 1, d[i + 1])); }
bool Med; signed main() { #ifdef LOCAL freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout); atexit([]{fprintf(stderr,"%.lfms\n%lfMB\n",clock()*1000./CLOCKS_PER_SEC,(&Mbe-&Med)/1024./1024);}); #endif
n = read(); init(2 * n); for(i = 1; i <= n; ++i) a[i] = read(); ans = 1; for(i = 1; i <= n; ++i) { calc(i - 1, -1); calc(i, -1); if(a[i] > 1) calc(i + a[i] - 1, -1); calc(i + a[i], -1); d[i]--; d[i + a[i]]++; calc(i - 1, 1); calc(i, 1); if(a[i] > 1) calc(i + a[i] - 1, 1); calc(i + a[i], 1); printf("%lld ", ans); } return 0; }
|