MX 暑假集训 8.2
ハイドレンジア(绣球花) - LonePi feat.歌愛ユキ
AT:C+D>E+F,这 EF 放的两个套路题何意味,CD 还吃了两发罚时,无语。
CF: B>C+D 的来了,被 B 卡了半小时 CD 加起来不到半小时,因为写得太慢所以 +63。
组合计数 Day2。
Beautiful Matrix
题意
定义一个 \(n\times n\) 的矩阵是漂亮的当且仅当每行都是 \(1\sim n\) 的排列且相邻两行同一列上的数不同,给定一个 \(n\times n\) 的漂亮的矩阵 \(a\),按行优先字典序比较,求字典序比它小的漂亮矩阵的个数,模数 \(998244353\)。
\(1\le n\le 2000\)。
solution
考虑枚举第一个小于给定矩阵的位置,前面的位置与给定矩阵相等,后面行的排列在满足相邻两行同一列上的数不同的前提下没有其他限制,当前行的限制比较多,要求构成排列的同时当前位置小于给定矩阵的位置,且这行后面每一列的元素不能与上面行相同列的元素相同。
后面行每行的排列数量都是长度为 \(n\) 的错排数量,若后面有 \(k\) 行,则方案数为 \({D_n}^k\),前面的行与给定矩阵相同,方案数为 \(1\)。
令答案矩阵为 \(b\),对于枚举到的位置 \((i,j)\),我们要求这一个位置的元素 \(<a_{i,j}\),此行后面每个位置 \((i,k)\) 有 \(b_{i,k}\ne a_{i-1,k}\)(因为第 \(i-1\) 行 \(a,b\) 构成相同),发现后面的方案数实际上只与后面空位的数量和有效的限制数量有关,若对于后面的一个位置 \((i,k)\),\(a_{i-1,k}\) 在 \(a_{i,x}(x\le j)\) 就出现过了,那么相当于 \((i,k)\) 没有限制,所以有效限制数量即为 \(a_{i-1,j+1\sim n}\) 中不在 \(a_{i,1\sim j}\) 中出现的数的数量,于是令后面剩下 \(x\) 个空位,有 \(y\) 个位置有限制的合法放置方案数为 \(f_{x,y}\)。
考虑 \(f_{i,j}\) 的转移,若 \(i=j\) 就是错排数量,\(f_{i,i}\leftarrow (i-1)\times (f_{i-1,i-1}+f_{i-2,i-2})\),否则钦定第 \(i\) 个位置没有限制,那么这个位置可以填 \(j\) 个有限制的数中的一个或者 \(i-j\) 个无限制数中的一个,方案数分别对应 \(j\times f_{i-1,j-1}\) 和 \((i-j)\times f_{i-1,j}\),所以转移即为 \(f_{i,j}\leftarrow j\times f_{i-1,j-1}+(i-j)\times f_{i-1,j}\),\(O(n^2)\) 预处理出来即可对每个位置 \(O(1)\) 查询。
还需要考虑的就是 \(j\) 所选的值的个数及是否在 \(a_{i-1,j+1\sim n}\) 中出现,统计每行时使用树状数组动态更新在 \(a_{i-1,j+1\sim n}\) 出现数的个数及 \(j\) 可行的取值,分成出现和不出现两种情况分别计算即可。
时间复杂度 \(O(n^2\log n)\)。
Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e3+10;
constexpr int inf=1e9+10;
constexpr int mod=998244353;
inline int ad(int x,int y) {return ((x+y>=mod)?(x+y-mod):(x+y));}
inline void add(int &x,int y) {x=ad(x,y);}
inline void read(int &x) {
x=0;
char ch=getchar();
while (ch<48) ch=getchar();
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
int n;
int a[N][N],f[N][N];
ll fac[N];
struct BIT {
int v[N],r[N];
inline void add(int p) {
if (!r[p]) return;r[p]=0;
for(;p<=n;p+=(p&-p))v[p]--;
}
inline void init() {
f(i,1,n) r[i]=1,v[i]=0;
f(i,1,n) for(int p=i;p<=n;p+=(p&-p)) v[p]++;
}
inline int qry(int p) {int r=0;for(;p;p-=(p&-p))r+=v[p];return r;}
inline bool chk(int p,int y) {return r[p]&&p<=y;}
}tr1,tr2;
int main() {
read(n);
f(i,1,n) f(j,1,n) read(a[i][j]);
f[0][0]=f[1][0]=1;
f(i,2,n) {
f[i][i]=1ll*(i-1)*(f[i-1][i-1]+f[i-2][i-2])%mod;
f[i][0]=1ll*f[i-1][0]*i%mod;
f(j,1,i-1) f[i][j]=(1ll*j*f[i-1][j-1]+1ll*(i-j)*f[i-1][j])%mod;
}
fac[0]=1;
f(i,1,n) fac[i]=fac[i-1]*f[n][n]%mod;
ll ans=0;
f(i,1,n) {
ll sum=0;
if (i!=1) tr1.init();
tr2.init();
f(j,1,n) {
tr1.add(a[i-1][j]);
ll c0=tr1.qry(a[i][j]-1),c1=tr2.qry(a[i][j]-1)-tr2.chk(a[i-1][j],a[i][j]-1)-c0,s=tr1.qry(n);
ll lsum=sum;
if (s) sum+=c0*f[n-j][s-1]%mod;
sum+=c1*f[n-j][s]%mod;
tr1.add(a[i][j]);
tr2.add(a[i][j]);
}
ans+=sum%mod*fac[n-i]%mod;
}
printf("%d\n",(int)(ans%mod));
return 0;
}
Interval-Free Permutations
析合树:博客,看起来是个很牛的东西。
题意
对于一个长度为 \(n\) 的排列 \(p\) 称一个区间 \([l,r]\) 为一个连续段当且仅当 \(\max\limits_{i=l}^{r}p_i-\min\limits_{i=l}^{r}p_i=r-l\)。
一个连续段是非平凡连续段当且仅当其长度在 \([2,n-1]\) 内。
求有多少个长度为 \(n\) 的排列满足不存在非平凡连续段。
\(1\le n\le 400\)。
solution
等价于求有多少个排列满足其析合树的根为析点且直接拥有 \(n\) 个叶子节点。
令 \(n\) 对应该问题的答案为 \(A_n\)。
不合法的情况有两种:根为合点或根为析点且儿子数量不为 \(n\),分开单独考虑。
若根为合点,则一定能找到一个前缀 \([1,i](i<n)\) 满足 \([1,i]\) 是一个连续段。
令 \(f_i\) 表示长度为 \(i\),且不存在 \(j<i\) 使得 \([1,j]\) 是一个连续段的排列数量,那么转移:\(f_i=i!-\sum\limits_{j=1}^{i-1}f_j\times (i-j)!\)。
那么根为合点的排列数量就为 \(2\times \sum\limits_{j=1}^{i-1}f_j\times (i-j)!\),乘 \(2\) 的意义是合点子节点值域单调递增和单调递减一一对应。
那么考虑根为析点且子节点数量不为 \(n\) 的排列数。
令 \(g_{i,j}\) 表示将 \(i\) 个叶子分成 \(j\) 个块,每个块内顺序任意的方案数,那么枚举最后一个块放了多少个叶子可以得到转移:\(g_{i,j}\leftarrow \sum\limits_{k=1}^{i} g_{i-k,j-1}\times k!\)。
由于析点要求任意两个连续子节点不能构成连续段,所以各个子节点之间应满足不构成连续段,若有 \(s\) 个子节点,则对应方案数正好为 \(A_s\)。
所以根为析点且儿子数量不为 \(i\) 的方案数为 \(\sum\limits_{j=4}^{i-1}g_{i,j}\times A_j\)。
所以答案的递推式:\(A_i=i!-2\times \sum\limits_{j=1}^{i-1}f_j\times (i-j)!-\sum\limits_{j=4}^{i-1}g_{i,j}\times A_j\)。
时间复杂度 \(O(n^3)\)。
Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=400+10;
constexpr int inf=1e9+10;
int mod;
inline int ad(int x,int y) {return (x+y>=mod)?(x+y-mod):(x+y);}
inline void add(int &x,int y) {x=ad(x,y);}
inline void read(int &x) {
x=0;
char ch=getchar();
while (ch<48) ch=getchar();
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
int f[N],g[N][N],ans[N],fac[N];
inline void solve(int n) {
fac[0]=1;
f(i,1,n) {
ll sum=0;
f(j,1,i-1) sum+=1ll*f[j]*fac[i-j]%mod;
add(f[i]=(fac[i]=1ll*fac[i-1]*i%mod),mod-sum%mod);
}
g[0][0]=1;
f(i,1,n) f(j,1,i) f(k,1,i) add(g[i][j],1ll*g[i-k][j-1]*fac[k]%mod);
ans[1]=1;ans[2]=2;
f(i,4,n) {
ll sum=0;
f(j,1,i-1) sum+=1ll*f[j]*fac[i-j]%mod;
sum*=2;
f(j,4,i-1) sum+=1ll*g[i][j]*ans[j]%mod;
add(ans[i]=fac[i],mod-sum%mod);
}
}
int main() {
int T;read(T);read(mod);
solve(400);
while (T--) {
int x;read(x);
printf("%d\n",ans[x]);
}
return 0;
}
纯洁憧憬
题意
对于一个长度为 \(n\) 的排列 \(p\) 称一个区间 \([l,r]\) 为一个连续段当且仅当 \(\max\limits_{i=l}^{r}p_i-\min\limits_{i=l}^{r}p_i=r-l\)。
一个连续段是非平凡连续段当且仅当其长度在 \([2,n-1]\) 内。
求有多少个长度为 \(n\) 的排列满足至少存在一个非平凡连续段长度大于 \(k\)。
\(1\le k<n\le 400\)。
solution
和上题基本一样。
题目相当于限定析合树要么根节点为析点且存在子节点长度大于 \(k\),要么根节点为合点且第一个或最后一个子节点长度小于 \(n-k\)。
存在转成所有,考虑求出不合法方案数然后容斥。
先 \(O(n^3)\) 求出 \(f,g,A\) 三个数组(定义同上题)。
若根节点为合点,则要求第一个和最后一个子节点长度不小于 \(n-k\),于是枚举这两个节点的长度,方案数即为:
若根节点为析点,定义 \(h_{i,j}\) 表示将 \(i\) 个叶子节点划分成 \(j\) 个块,每个块长度不超过 \(k\) 且块内任意排布的方案数,转移类似于 \(g\) 数组的转移,枚举最后一个块的大小时加上 \(k\) 的限制即可:
根节点为析点的方案数即为:
用总方案数减去这两种不合法方案数即可,时间复杂度 \(O(n^3)\)。
Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=400+10;
constexpr int inf=1e9+10;
constexpr int mod=1e9+7;
inline int ad(int x,int y) {return ((x+y>=mod)?(x+y-mod):(x+y));}
inline void add(int &x,int y) {x=ad(x,y);}
inline void read(int &x) {
x=0;
char ch=getchar();
while (ch<48) ch=getchar();
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
int n,m;
int f[N],g[N][N],h[N][N],A[N],fac[N];
int main() {
read(n);read(m);
fac[0]=1;
f(i,1,n) {
ll sum=0;
f(j,1,i-1) sum+=1ll*f[j]*fac[i-j]%mod;
add(f[i]=(fac[i]=1ll*fac[i-1]*i%mod),mod-sum%mod);
}
g[0][0]=1;
f(i,1,n) f(j,1,i) f(k,1,i) add(g[i][j],1ll*g[i-k][j-1]*fac[k]%mod);
A[1]=1;A[2]=2;
f(i,4,n) {
ll sum=0;
f(j,1,i-1) sum+=1ll*f[j]*fac[i-j]%mod;
sum*=2;
f(j,4,i-1) sum+=1ll*g[i][j]*A[j]%mod;
add(A[i]=fac[i],mod-sum%mod);
}
h[0][0]=1;
f(i,1,n) f(j,1,i) for (int k=1,mx=min(i,m);k<=mx;k++) add(h[i][j],1ll*h[i-k][j-1]*fac[k]%mod);
ll ans=0;
f(i,n-m,n) f(j,n-m,n) if (i+j<=n) ans+=1ll*f[i]*f[j]%mod*fac[n-i-j]%mod;
ans*=2;
f(i,4,n) ans+=1ll*h[n][i]*A[i]%mod;
printf("%d\n",ad(fac[n],mod-ans%mod));
return 0;
}
Beautiful Bracket Sequence (hard version)
题意
定义一个合法括号序列的深度如下:
- 空字符串是一个合法括号序列,深度为 \(0\)。
- 如果 \(s\) 是一个深度为 \(d\) 的合法括号序列,那么 \((s)\) 是一个深度为 \(d+1\) 的合法括号序列。
- 如果 \(s\) 和 \(t\) 都是合法括号序列,那么它们的连接 \(st\) 也是合法括号序列,其深度为 \(s\) 和 \(t\) 的最大深度。
定义一个(不一定合法)的括号序列的深度为,任意删除若干字符后得到括号序列深度最大值。
给定长度为 \(n\) 且一个仅包含 '('、')' 和 '?' 的字符串,求所有将 '?' 替换成 '('、')' 的方案得到的括号序列深度之和,对 \(998244353\) 取模。
\(1\le n\le 10^6\)。
solution
对于一个长度为 \(n\) 的确定括号序列(没有问号) \(s\),由于最终决定这个括号序列深度的字符实际上是一段前缀的左括号和一段后缀的右括号,记 \(a_i,b_i\) 分别为 \([1,i]\) 的左括号数和 \([i+1,n]\) 的右括号数,所以可以推出它的深度为 \(\max\limits_{i=1}^n(\min(a_i,b_i))\)。
但是这样刻画一个串的深度可能会使得它在 \(i\) 不同时都达到最大深度,从而难以计数,我们发现一定有 \((a_{i+1}-b_{i+1})-(a_i-b_i)=1\)(讨论 \(i+1\) 位置上是左括号还是右括号即可),那么对于一个串,我们希望在 \(a_i-b_i=0\)(即 \(a_i=b_i\))时计入答案,这样答案就只会被计入一次。
枚举 \(i\),令 \(a\) 为 \([1,i]\) 的左括号数,\(b\) 为 \([i+1,n]\) 的右括号数,\(x\) 为 \([1,i]\) 的问号数,\(y\) 为 \([i+1,n]\) 的问号数。
对于这个位置,我们枚举 \(j\) 表示统计深度为 \(j\) 且在 \(i\) 处计入的方案数,那么在 \(i\) 处计入的总贡献为:
直接枚举 \(j\) 是 \(O(n^2)\) 的,可以通过简单版,考虑优化这个式子。
发现这个 \(j\) 很难搞,于是换成 \(j-a+a\)。
由于 \(m\times \binom{n}{m}=\frac{n!m}{m!(n-m)!}=\frac{(n-1)!n}{(m-1)!(n-m)!}=n\binom{n-1}{m-1}\),所以可以写成:
到这里就可以用范德蒙德卷积了:
于是对于每个 \(i\) 可以 \(O(1)\) 求出贡献。
时间复杂度 \(O(n)\)。
Code
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=1e6+10;
constexpr int inf=1e9+10;
constexpr int mod=998244353;
inline int ad(int x,int y) {return (x+y>=mod)?(x+y-mod):(x+y);}
inline void add(int &x,int y) {x=ad(x,y);}
inline ll qpow(ll a,int b) {
ll res=1;
while (b) {
if (b&1) res=res*a%mod;
a=a*a%mod;b>>=1;
}
return res;
}
inline void read(int &x) {
x=0;
char ch=getchar();
while (ch<48) ch=getchar();
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
ll fac[N],inv[N];
inline void init(int n) {
fac[0]=inv[0]=1;
f(i,1,n) fac[i]=fac[i-1]*i%mod;
inv[n]=qpow(fac[n],mod-2);
F(i,n-1,1) inv[i]=inv[i+1]*(i+1)%mod;
}
inline ll C(int n,int m) {
if (n<0||m<0||n<m) return 0;
return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
int n;
int s1[N],s2[N],s3[N];
char s[N];
int main() {
scanf("%s",s+1);
n=strlen(s+1);init(n);
f(i,1,n) {
s1[i]=s1[i-1]+(s[i]=='(');
s2[i]=s2[i-1]+(s[i]==')');
s3[i]=s3[i-1]+(s[i]=='?');
}
ll ans=0;
f(i,1,n) {
int x=s3[i],y=s3[n]-s3[i];
int a=s1[i],b=s2[n]-s2[i];
ans+=(1ll*x*C(x+y-1,y+b-a-1)+1ll*a*C(x+y,y+b-a))%mod;
}
printf("%d\n",(int)(ans%mod));
return 0;
}
Student's Camp
题意
有 \(n+2\) 行 \(m\) 列的方块组成的矩形,一次吹风会使矩阵除了第一行和最后一行以外的每行最左端和最右端未被吹走的格子分别有 \(p\) 的概率被吹走,求 \(k\) 次吹风后第一行与最后一行仍连通的概率,模数 \(1e9+7\)。
\(1\le n,m\le 1500,1\le k\le 10^5\)。
solution
考虑 \(dp\),令 \(f_{i,l,r}\) 表示第一行到第 \(i\) 行连通,且第 \(i\) 行保留了 \([l,r]\) 的所有方块未被吹走的概率,令 \(g_i\) 表示 \(k\) 次吹风成功吹走 \(i\) 次的概率,那么有以下转移:
发现 \([l',r']\cap [l,r]\ne \emptyset\) 这个条件实际上可以写成前缀和的形式,令 \(s_{i,x}=\sum\limits_{l\le r\le x}f_{i,l,r}\),那么上面的式子可以优化成:\(f_{i,l,r}\leftarrow g_{l-1}g_{m-r}(s_{i-1,m}-s_{i-1,l-1}-s_{i-1,m-r})\),意思是总方案数减去 \(r'<l\) 的方案数和 \(l'>r\) 的方案数,其中 \(l'>r\) 的方案数和 \(l'\le m-r\) 的方案数完全等价,所以可以看作 \(s_{i-1,m-r}\),最后要求的是 \(s_{n,m}\)。
考虑直接对 \(s\) 做转移,这样状态数只有 \(n^2\),\(f_{i,l,r}\) 能贡献到所有 \(s_{i,j}(j\ge r)\) 里,所以有以下转移:
转移时预处理出 \(\sum\limits_{l=1}^{j}g_{l-1}\) 和 \(\sum\limits_{l=1}^j (g_{l-1}\times s_{i-1,l-1})\),即可做到时间复杂度 \(O(nm)\)。
Code
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=1500+10;
constexpr int K=1e5+10;
constexpr int inf=1e9+10;
constexpr int mod=1e9+7;
inline int ad(int x,int y) {return ((x+y>=mod)?(x+y-mod):(x+y));}
inline void add(int &x,int y) {x=ad(x,y);}
inline ll qpow(ll a,int b) {
ll res=1;
while (b) {
if (b&1) res=res*a%mod;
a=a*a%mod;b>>=1;
}
return res;
}
inline void read(int &x) {
x=0;
char ch=getchar();
while (ch<48) ch=getchar();
while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}
int n,m,p,k;
int f[N],g[N],s1[N],s2[N];
int main() {
read(n);read(m);
int a,b;read(a);read(b);
p=1ll*a*qpow(b,mod-2)%mod;
read(k);
for (int i=0,C=1,len=m;i<=len;i++) {
if (i<=k) g[i]=C*qpow(p,i)%mod*qpow(mod+1-p,k-i)%mod;
C=1ll*C*qpow(i+1,mod-2)%mod*(k-i)%mod;
if (i) add(s1[i]=s1[i-1],g[i-1]);
}
f[m]=1;
f(i,1,n) {
static ll tmp[N];
f(j,1,m) tmp[j]=f[j];
f(j,1,m) {
add(f[j]=f[j-1],tmp[m]*g[m-j]%mod*s1[j]%mod);
add(f[j],mod-1ll*g[m-j]*s2[j]%mod);
add(f[j],mod-tmp[m-j]*g[m-j]%mod*s1[j]%mod);
add(s2[j]=s2[j-1],1ll*f[j-1]*g[j-1]%mod);
}
}
printf("%d\n",f[m]);
return 0;
}

浙公网安备 33010602011771号