首页 > 技术文章 > AGC055 题解

Appleblue17 2021-11-07 22:41 原文

However, I have to warn you that contest will be closer to ABC than to AGC...

Atcoder Genius Contest

子序列专题赛???

A. ABC Identity

  • 定义字符串 \(T\)好的,当且仅当它能够被分为等长且不同的三段,每段由同种字母组成。

  • 给定由 \(N\)\(A\)\(N\)\(B\)\(N\)\(C\) 组成的字符串 \(S\),你需要将其分为不超过 \(6\)好的 子序列。

  • \(1 \leq N \leq 2 \times 10^5\)


开幕构造题

看上去很无法下手,不妨先考虑只有 \(A\)\(B\) 的情况。

猜想 \(6\)\(3!\),大概是每种子序列都有一个。所以考虑将 \(N\)\(A\)\(N\)\(B\) 构成的字符串分 ABBA 子序列。

仔细思考一下,可以如此贪心:

取最大的 \(k\),使得可以选出前 \(k\)\(A\) 和后 \(k\)\(B\) 组成一个好的子序列。

这样挑出来以后剩下的一定是 BB...BA..AA,所以是正确的。

回到原题,不妨先将 ABCACB 两种一起考虑。将 \(B\)\(C\) 视为同种字母,并取最大的 \(k\) 使可以选出前 \(k\)\(A\),后 \(k\)\(B\) 和后 \(k\)\(C\)

选出后再故技重施将 \(B\)\(C\) 分为 BC 子序列与 CB 子序列,与前面的 \(A\) 配对即可。其它两种同理。

时间复杂度 \(O(N)\)

#include<bits/stdc++.h>
using namespace std;
const int N=6e5+5,INF=1e9;
int n;
char S[N];
int ans[N];
bool vis[N];
int L[N],R[N],cntl,cntr;

int main(){
	cin>>n;
	n*=3;
	scanf("\n%s",S+1);
	for(int t=0;t<=2;t++){
		int l=1,r,r1=n,r2=n;
		cntl=cntr=0;
		while(1){
			int ll=l,rr1=r1,rr2=r2,x,y,z;
			while(ll<=rr1 && ll<=rr2 && (vis[ll] || S[ll]!='A'+t)) ll++;
			x=ll;
			while(ll<=rr1 && (vis[rr1] || S[rr1]!='A'+(t+1)%3)) rr1--;
			y=rr1;
			while(ll<=rr2 && (vis[rr2] || S[rr2]!='A'+(t+2)%3)) rr2--;
			z=rr2;
			if(ll>rr1 || ll>rr2) break;
			L[++cntl]=x,R[++cntr]=y,R[++cntr]=z;
			vis[x]=vis[y]=vis[z]=1;
			l=ll+1,r1=rr1-1,r2=rr2-1;
		}
		sort(R+1,R+cntr+1);
		l=1,r=cntr;
		int ct=0;
		while(l<r){
			int ll=l,rr=r,x,y;
			while(ll<=rr && S[R[ll]]!=S[R[1]]) ll++;
			x=ll;
			while(ll<=rr && S[R[rr]]==S[R[1]]) rr--;
			y=rr;
			if(ll>rr) break;
			ans[L[++ct]]=ans[R[ll]]=ans[R[rr]]=t*2+1;
			l=ll+1,r=rr-1;
		}
		for(int i=1;i<=cntl;i++) if(!ans[L[i]]) ans[L[i]]=t*2+2;
		for(int i=1;i<=cntr;i++) if(!ans[R[i]]) ans[R[i]]=t*2+2;
	}
	for(int i=1;i<=n;i++) cout<<ans[i];
	return 0;
}

B. ABC Supremacy

  • 定义一次操作为:选择任意一个为 ABCBCACAB 之一的子串,将其替换为 ABCBCACAB 之一,

  • 判断能否通过有限次操作将 \(S\) 变为 \(T\)

  • \(|S|=|T|=N\)\(1 \leq N \leq 5 \times 10^5\)


全场最简单

为了方便,将位置为 \(i\) 的字母 \(c\)\(c\)\(0,1,2\) 之一)替换为 \((c-i) \bmod 3\)。这样 ABCBCACAB 就变成了 AAABBBCCC

观察一下样例,发现有一个很好的性质:

子串 XYYY 可以通过操作变为 YYYX。(XYYY->XXXX->YYYY->YYYX

也就是说,这种子串是可以任意移动的。

直接把所有 三个字母相同的子串 移到左边并删掉,再判断剩下 \(S'\)\(T'\) 是否相同即可。

实现时把字母依次扔进栈里,如果栈顶的三个字母相同就一起弹掉。

时间复杂度 \(O(N)\)

#include<bits/stdc++.h>
using namespace std;
const int N=6e5+5,INF=1e9;
int n;
char S[N],T[N];
int s[N],t[N],tot1,tot2;
int ss[N],tt[N],cnts,cntt;

int main(){
	cin>>n;
	scanf("\n%s",S+1);
	scanf("\n%s",T+1);
	for(int i=1;i<=n;i++) s[i]=S[i]-'A',t[i]=T[i]-'A';
	for(int i=1;i<=n;i++){
		ss[++cnts]=s[i];
		if(cnts>=3 && ss[cnts-2]==(ss[cnts]+1)%3 && ss[cnts-1]==(ss[cnts]+2)%3) cnts-=3;
	}
	for(int i=1;i<=n;i++){
		tt[++cntt]=t[i];
		if(cntt>=3 && tt[cntt-2]==(tt[cntt]+1)%3 && tt[cntt-1]==(tt[cntt]+2)%3) cntt-=3;
	}
	if(cnts!=cntt){
		puts("NO");
		return 0;
	}
	for(int i=1;i<=cnts;i++)
		if(ss[i]!=tt[i]){
			puts("NO");
			return 0;
		}
	puts("YES");
	return 0;
}

C. Weird LIS

  • 求长度为 \(N\) 的数组 \(A\) 的数量,满足:

    • \(2 \leq A_i \leq M(1 \leq i \leq N)\)

    • 存在长度为 \(N\) 的排列 \(P\),使得 \(A_i\) 为去掉 \(P_i\)\(P'\) 的最长上升子序列长度(\(LIS\))。

  • \(3 \leq N \leq 5000\)\(2 \leq M \leq N-1\)\(P \in \text{Prime}\)


场上想了半天都没整清楚,赛后才想到正解

设原排列的 \(LIS\)\(X\),删掉一个位置后 \(LIS\) 至多只会减少 \(1\),因此 \(A_i \in \{X-1,X\}\)

接下来考虑枚举 \(X \in [3,M]\),不过还有两种特殊情况要特判掉:

  • \([2,2,\cdots,2]\),当且仅当 \(M=3\) 时发生。

  • \([M,M,\cdots,M]\),当且仅当 \(M=N-1\) 时发生。

为了方便,对于每个位置 \(i\),如果 \(A_i=X-1\),则记为 O,否则记为 X

考虑在求 \(LIS\) 时,设 \(f_i\) 表示以 \(i\) 结尾的 \(LIS\) 长度。若 \(i\)\(j\) 转移而来(即 \(j<i\)\(P_j<P_i\)),就画一条由 \(j\) 连向 \(i\) 的边。

显然最后的图会形成一个 \(DAG\),而一个位置为 O 当且仅当拓扑排序时它所在的那一层只有它一个位置。

所以如果一个 OX 序列存在,等价于可以画出这样的图(注意该图只是 \(P\) 的一个子序列):

而其中最长链的长度为 \(X\)

接下来考虑对 OX 序列数数。大力 dp 显然不现实而且会算重,考虑直接用组合数算。

上图只是子序列,这让我们难以计数。不妨直接考虑整个 \(P\),其对 \(LIS\) 长度的贡献规则为:

  • 一个 O 的贡献为 \(1\)

  • 连续的 \(l\)X 的贡献为 \(w \in [0,\lfloor\dfrac{l}{2}\rfloor]\)(任选)。

直接枚举序列最长可能的 \(LIS\)\(k\)(即每个 \(w\) 都取 \(\lfloor\dfrac{l}{2}\rfloor\)),O\(t\) 个。这样这个序列能贡献到的 \(X \in [t,k]\)

为了不算漏,假设第 \(N+1\) 个位置为 O

现在先将 k-tXX 插入到 t+1O 前面。用插板法,方案数为 \(\dbinom{k}{t}\)

接下来为了使序列长度为 \(N\),还需要插入 \(N-t-2(k-t)=N-2k+t\)X,每个 O 前面最多只能插一个这种 X(不然就会对 \(LIS\) 产生贡献)。方案数显然就是 \(\dbinom{t+1}{N-2k+t}\)

最后,我们只需要对 \(X \in [3,M]\) 求和,所以可以直接 \(O(1)\) 算出每一个状态对答案的贡献。最后的答案为:

\[Ans=[n>3]+[n=m+1]+\sum\limits_{k=1}^N \sum\limits_{t=0}^{2k-N} \max\{0,\min\{k,m\}-\max\{t,3\}+1\}\dbinom{k}{t}\dbinom{t+1}{N-2k+t} \]

时间复杂度 \(O(n^2)\)

#include<bits/stdc++.h>
using namespace std;
const int N=5500;
int n,m,mod;
int C[N][N],f[N],ans;

int main(){
	cin>>n>>m>>mod;
	for(int i=0;i<N;i++){
		C[i][0]=1;
		for(int j=1;j<=i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
	}
	for(int k=3;k<=n;k++)
		for(int t=0;t<=k;t++)
			if(n-2*k+t>=0) ans=(ans+1ll*max(0,(min(k,m)-max(t,3)+1))*C[k][t]%mod*C[t+1][n-2*k+t]%mod)%mod;
	cout<<(ans+(n==m+1)+(n!=3))%mod;
}

D. ABC Ultimatum

  • 称一个字符串为好的,当且仅当它能被拆分成若干子序列,使得每个子序列为 ABCBCACAB 之一。

  • 给定由 ABC? 组成的长度为 \(3N\) 的字符串 \(S\),求将 ? 填好后产生的好字符串数量。

  • \(1 \leq N \leq 15\)\(P \in \text{Prime}\)


MO 题属于是

重点显然在于如何判断一个串是否是好的。

思考之中,你灵光一闪,猜想出一个结论(所以这个结论到底是怎么想到的啊):

\(s_A(i)\)\(s_B(i)\)\(s_C(i)\) 分别为 \(S_0,\cdots,S_i\)\(A\)\(B\)\(C\) 的个数,设 \(M_A=\max\limits_{i=0}^{3N}\{s_B(i)-s_A(i)\}\)\(M_B=\max\limits_{i=0}^{3N}\{s_C(i)-s_B(i)\}\)\(M_C=\max\limits_{i=0}^{3N}\{s_A(i)-s_C(i)\}\),则 \(S\) 是好的当且仅当 \(M_A+M_B+M_C \leq N\)

这个结论很诡异,所以我们要证明它:

  • 必要性

    如果 \(S\) 是好的,设它被拆分成 \(a\)ABC\(b\)BCA\(c\)CAB

    只有 BCABA 前面,所以肯定有 \(M_A \leq b\),同理 \(M_B \leq c\)\(M_C \leq a\)

    相加即可得 \(M_A+M_B+M_C \leq a+b+c=N\)

  • 充分性

    \(S\) 复制成三份,成为长度为 \(9N\)\(S'\)

    定义 \(i\)-th X 表示 \(S'\) 中第 \(i\) 个字母 X。

    下面证明如果 \(M_A+M_B+M_C \leq N\),则可以选出 \(N\) 个子序列 (\(i\)-th A, \(i+M_A\)-th B, \(i+M_A+M_B\)-th C)。

    由于是轮换的,只需要证明 \(i\)-th A \(\leq\) \(i+M_A\)-th B \(\leq\) \(i+M_A+M_B\)-th C \(\leq\) \(i+N\)-th A。(\(X \leq Y\) 表示 \(X\)\(Y\) 前面。

    \(M_A\) 的定义,\(i\)-th A \(\leq\) \(i+M_A\)-th B。

    同理,\(i\)-th B \(\leq\) \(i+M_B\)-th C,所以就有 \(i+M_A\)-th B \(\leq\) \(i+M_A+M_B\)-th C。

    同理也有 \(i+M_A+M_B\)-th C \(\leq\) \(i+M_A+M_B+M_C\)-th A \(\leq\) \(i+N\)-th A,证毕。

证明这个结论后即可记录状态 dp。

看似有 \(7\) 个状态(\(i\)\(A\)\(B\)\(C\)\(M_A\)\(M_B\)\(M_C\)),但实际上 \(A+B+C=0\),因此只记录 \(6\) 个状态转移即可。

时间复杂度 \(O(4n^6)\)(因为 \(A\)\(B\) 的值域是 \([-N,N]\)),常数很小。

6 维 dp 是吧

#include<bits/stdc++.h>
using namespace std;
const int N=16,mod=998244353;
int n,ans;
char S[N*3];
int dp[N*3][N*2][N*2][N][N][N];

int main(){
	cin>>n;
	scanf("\n%s",S);
	dp[0][n][n][0][0][0]=1;
	for(int t=0;t<3*n;t++)
		for(int a=0;a<=2*n;a++)
			for(int b=0;b<=2*n;b++)
				for(int ma=0;ma<=n;ma++)
					for(int mb=0;mb<=n;mb++)
						for(int mc=0;mc<=n;mc++){
							if(!dp[t][a][b][ma][mb][mc]) continue;
							int na,nb;
							na=a-1,nb=b;
							if(a>0 && (S[t]=='?' || S[t]=='A'))
								dp[t+1][na][nb][max(ma,na-n)][max(mb,nb-n)][max(mc,2*n-na-nb)]
								=(dp[t+1][na][nb][max(ma,na-n)][max(mb,nb-n)][max(mc,2*n-na-nb)]+dp[t][a][b][ma][mb][mc])%mod;
							
							na=a+1,nb=b-1;
							if(b>0 && a<2*n && (S[t]=='?' || S[t]=='B'))
								dp[t+1][na][nb][max(ma,na-n)][max(mb,nb-n)][max(mc,2*n-na-nb)]
								=(dp[t+1][na][nb][max(ma,na-n)][max(mb,nb-n)][max(mc,2*n-na-nb)]+dp[t][a][b][ma][mb][mc])%mod;
							
							na=a,nb=b+1;
							if(b<2*n && (S[t]=='?' || S[t]=='C'))
								dp[t+1][na][nb][max(ma,na-n)][max(mb,nb-n)][max(mc,2*n-na-nb)]
								=(dp[t+1][na][nb][max(ma,na-n)][max(mb,nb-n)][max(mc,2*n-na-nb)]+dp[t][a][b][ma][mb][mc])%mod;
						}
	for(int ma=0;ma<=n;ma++)
		for(int mb=0;mb<=n;mb++)
			for(int mc=0;mc<=n;mc++)
				if(ma+mb+mc<=n) ans=(ans+dp[3*n][n][n][ma][mb][mc])%mod;
	cout<<ans;
	return 0;
}

E. Set Merging

  • \(N\) 个集合 \(S_1,S_2,\cdots,S_N\),初始时 \(S_i=\{i\}\)

  • 定义一次操作为:选择 \(i \in [1,N-1]\),使 \(S_i,S_{i+1} \leftarrow S_i \bigcup S_{i+1}\)

  • 求出最少操作次数使得 \(\forall i \in [1,N], S_i=\{L_i,L_i+1,\cdots,R_i-1,R_i\}\),或判断无解。

  • \(1 \leq N \leq 5 \times 10^5\)\(1 \leq L_i \leq R_i \leq N\)


《0 人 A C》

其实好像也不是很难,但真的很巧妙

显然任意时刻任意集合内的元素都是一个区间,所以可以对左右边界分别考虑。

由于每次只能操作相邻的两个集合,\(l_i\)\(r_i\) 都一定是单调不降的,这提醒我们可以借助别的东西来维护它们。

一个特别巧妙的构造:

设排列 \(P=[1,2,\cdots,N]\)。操作 \(S_i\)\(S_{i+1}\) 时,交换 \(P_i\)\(P_{i+1}\)。则任意时刻 \(l_i=\min\limits_{t \geq i} P_t\)\(r_i=\max\limits_{t \leq i} P_t\)

正确性比较显然,以左端点为例证明:

假设某时刻该构造是成立的,交换 \(S_i\)\(S_{i+1}\)

  • \(l_i<l_{i+1}\),则有 \(P_i<P_{i+1}\)。交换后 \(l_i'=l_{i+1}'=l_i=P_i\),仍然成立。

  • \(l_i=l_{i+1}\),则有 \(P_i>P_{i+1}\)。交换后 \(l_i\)\(l_{i+1}\) 不变,\(P_i\)\(P_{i+1}\) 交换后也不会造成影响,成立。

问题转变为:知道 \(L_i\)\(R_i\),要求出符合题意的 \(P_i\) 逆序对最小值。

我们可以先把一些能确定的确定下来:若 \(L_i<L_{i+1}\),则 \(P_i=L_i\);若 \(R_i<R_{i+1}\),则 \(P_{i+1}=R_{i+1}\)

将这些确定后,对于未被确定的 \(x\),只需要满足 \(P_x \geq L_x\)\(P_x \leq R_x\)。由于 \(L\)\(R\) 都是单调不减的,显然从小到大从前到后依次填入是优秀的贪心。

与此同时,我们想要最小化逆序对个数,这样贪心的同时也达到了这个目的,一石二鸟。

所以填好 \(P\),检查一遍是否合法,用树状数组计算逆序对个数即可。

时间复杂度 \(O(n \log n)\)

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5,INF=1e9;
int n;
int L[N],R[N],P[N];
long long ans;
bool vis[N];

int c[N];
int lowbit(int x){
    return x & (-x);
}
void modify(int x,int k){
    while(x<N) c[x]+=k,x+=lowbit(x);
}
int query(int x){
    int tot=0;
    while(x) tot+=c[x],x-=lowbit(x);
    return tot;
}

int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>L[i]>>R[i];
        if(L[i]<L[i-1] || R[i]<R[i-1]) return puts("-1"),0;
    }
    for(int i=n;i>=1;i--)
        if(L[i]!=L[i+1])
            P[i]=L[i];
    for(int i=1;i<=n;i++)
        if(R[i]!=R[i-1])
            P[i]=R[i];
    
    for(int i=1;i<=n;i++){
    	if(!P[i]) continue;
        if(vis[P[i]]) return puts("-1"),0;
        vis[P[i]]=1;
    }
    int nw=1;
    for(int i=1;i<=n;i++){
        if(!P[i]){
            while(vis[nw]) nw++;
            P[i]=nw;
            vis[nw]=1;
        }
    }

    int mn=INF,mx=0;
    for(int i=n;i>=1;i--){
        mn=min(mn,P[i]);
        if(mn!=L[i]) return puts("-1"),0;
    }
    for(int i=1;i<=n;i++){
        mx=max(mx,P[i]);
        if(mx!=R[i]) return puts("-1"),0;
    }
    for(int i=1;i<=n;i++){
        ans+=i-1-query(P[i]);
        modify(P[i],1);
    }
    cout<<ans;
    return 0;
}

F. Creative Splitting

  • \(K\) 元数组 \([a_1,\cdots,a_K]\)好的,当且仅当 \(\forall i \in [1,K],a_i \leq i\)

  • \(NK\) 元数组 \([b_1,\cdots,b_{NK}]\)极好的,当且仅当 \(b\) 能被拆分成 \(N\)好的 子序列。

  • 对于每个 \(pos \in [1,NK]\)\(val \in [1,K]\),求出满足 \(b_{pos}=val\)极好的 数组 \(b\) 的数量。

  • \(1 \leq N,K \leq 20\)\(P \in \text{Prime}\)


《恐怖红黑蜜蜂》

与 D 一样,还是得先考虑如何判断一个数组是否是 极好的

显然 \(a\) 中越前面限制就越严格,所以我们可以从后往前考虑。

考虑一个贪心:

维护 \(N\) 个初始为空的子序列 \(S_1,S_2,\cdots,S_N\),从后往前加入元素。在 \(b\) 中从后往前枚举 \(b_i\),每次选择 \(b_i\) 能填入的 最长的 子序列加入,如果最长的有多个则选择编号最小的。
如果能将 \(b_i\) 全部顺利加入,则 \(b\)极好的,否则不是。

正确性似乎比较显然,貌似可以用调整法证明(?)。(没看懂官方题解,感觉有些漏洞)

接下来我们来考虑添加的过程。由贪心的过程,可以画出一个状态下一步加入的元素可能位置:

似乎就是把某一列添加 \(1\) 格?

如果我们把每一列看成是有序的,并在每一次操作后按该列格子数升序排序,那确实就是把某一列添加 \(1\) 格。

也就是说,可以对应到这样一个过程:

\(K\) 个互相区分的球初始在 \(0\),每次可以选择一个球移动 \(1\) 步,\(NK\) 步后将所有球移动到 \(N\)

(一个球对应一列)


现在我们不妨来开始考虑题目的限制。下面设 \(p_i\) 表示第 \(i\) 个球的长度。

\(pos-1\) 步后,能填入 \(val\) 就等价于 \(val\) 对应的这一列还未填满,似乎很好描述。

但是这样描述后我们并不清楚填入 \(val\) 的到底是哪一列,也无从知道每个 \(p_i\),无法计数。

所以考虑枚举在第 \(pos\) 步,我们将某个球从 \(x\) 移动到了 \(x+1\)

\(pre_{pos,k}\) 表示 \(pos\) 步后长度小于 \(k\) 的列的数量,那么 \(b_{pos}=val\) 等价于:\(pre_{pos-1,x} < val \leq pre_{pos-1,x+1}\)

为什么呢?请看下图:

清楚限制后,我们来考虑如何计算答案。

假设 \(pos-1\) 步后每个球的位置为 \(p=x,p_1,p_2,\cdots,p_{K-1}\),考虑分配每个球的步数。每个球是互相独立的,所以方案数就是可重排列:

\[\dfrac{(i-1)!}{p!p_1!p_2!\cdots p_{k-1}!}\dfrac{(NK-i)}{(N-p-1)!(N-p_1)!(N-p_2)!\cdots (N-p_{k-1})!} \]

只需要对所有 \(pre_{pos-1,x} < val \leq pre_{pos-1,x+1}\) 求出这个方案数的和即可。为了方便,记 \(val_i=\dfrac{1}{i!(N-i)!}\)

考虑枚举 \(l=pre_{pos-1,x}\)\(r=pre_{pos-1,x+1}\),就可以算出 \(<x\)\(p_i\)\(a=l\) 个,\(=x\)\(p_i\)\(b=r-l\) 个(包括 \(p\)),\(>x\)\(p_i\)\(c=K-r\) 个。

现在的限制仍然不够计算答案,再枚举 \(pos-1\) 步后 \(<x\)\(p_i\) (步数)之和为 \(A=sum\)\(=x\) 的步数为 \(B=(r-l)x\)\(>x\) 的步数为 \(C=pos-1-A-B\)

信息已经足够了,最后只需要预处理出 \(f_{i,lim,sum}\) 表示 \(i\) 个球,每个球的步数均 \(<lim\),共走 \(sum\) 步的方案数(上面所述的那个可重排列)之和;及\(g_{i,lim,sum}\) 表示 \(i\) 个球,每个球的步数均 \(>lim\),共走 \(sum\) 步的方案数,就可计算答案。

预处理直接按 \(i\) 枚举这个球走了多少步,dp 即可。

信息有点多,写在一起:

\[f_{i,lim,sum}=\sum\limits_{t=0}^N [lim>t] val_t f_{i-1,lim,sum-t} \]

\[g_{i,lim,sum}=\sum\limits_{t=0}^N [lim<t] val_t g_{i-1,lim,sum-t} \]

一个状态对答案的贡献:

\[\dbinom{k}{A,B,C}\dfrac{(pos-1)!(NK-pos)!}{x!(N-x-1)!} \sum\limits_{sum} f_{a,x,A} val_x^b g_{c,x,C} \]

下面来分析复杂度:

  • 枚举 \(pos(NK)\)\(x(N)\)
  • 枚举 \(l=pre_{pos-1,x}(K)\)\(r=pre_{pos-1,x+1}(K)\)
  • 枚举 \(A=sum(NK)\)

总时间复杂度 \(O(N^3K^4)\),勉强过得去。

这也太阴间了吧……

#include<bits/stdc++.h>
using namespace std;
const int N=22,K=22;
int n,k,mod;
int f[K][N][N*K],g[K][N][N*K],ans[N*K][N];
int mul[N*K],inv[N*K],val[N][K];

int ksm(int a,int x){
    int tot=1;
    while(x){
        if(x & 1ll) tot=1ll*tot*a%mod;
        a=1ll*a*a%mod;
        x>>=1ll;
    }
    return tot;
}
void init(int lim){
    mul[0]=inv[0]=1;
    for(int i=1;i<=lim;i++)
		mul[i]=1ll*mul[i-1]*i%mod;
    inv[lim]=ksm(mul[lim],mod-2);
    for(int i=lim-1;i>=1;i--) inv[i]=1ll*inv[i+1]*(i+1)%mod;
}
int C(int m,int n){
    if(m<0 || m<n) return 0;
    return 1ll*mul[m]*inv[n]%mod*inv[m-n]%mod;
}
inline int getmod(int x){
	if(x>=mod) x-=mod;
	return x;
}

int main(){
    cin>>n>>k>>mod;
    init(n*k);
    for(int lim=0;lim<=n;lim++) f[0][lim][0]=g[0][lim][0]=1;
    for(int i=0;i<=n;i++){
		int w=1ll*inv[i]*inv[n-i]%mod;
    	val[i][0]=1;
    	for(int j=1;j<=k;j++) val[i][j]=1ll*val[i][j-1]*w%mod;
	}
    for(int i=1;i<=k;i++){
        for(int lim=0;lim<=n;lim++){
            for(int sum=0;sum<=n*k;sum++){
                for(int t=0;t<=n;t++){
                    if(lim>t && sum>=t) f[i][lim][sum]=(f[i][lim][sum]+1ll*val[t][1]*f[i-1][lim][sum-t]%mod)%mod;
                    if(lim<t && sum>=t) g[i][lim][sum]=(g[i][lim][sum]+1ll*val[t][1]*g[i-1][lim][sum-t]%mod)%mod;
                }
            }
        }
    }
    
    for(int pos=1;pos<=n*k;pos++){
        for(int x=0;x<=n;x++){
            for(int l=0;l<=k;l++){
                for(int r=l+1;r<=k;r++){
                    int tot=0;
                    int a=l,b=r-l-1,c=k-r;
                    for(int sum=0;sum<=pos-1-(r-l)*x;sum++){
                        int sa=sum,sc=pos-1-sum-(r-l)*x;
                        tot=getmod(tot+1ll*f[a][x][sa]*g[c][x][sc]%mod);
                    }
                    tot=1ll*tot*val[x][b]%mod *mul[k]%mod*inv[a]%mod*inv[b+1]%mod*inv[c]%mod *inv[x]%mod*inv[n-x-1]%mod*mul[pos-1]%mod*mul[n*k-pos]%mod;
                    for(int t=l+1;t<=r;t++) ans[pos][t]=getmod(ans[pos][t]+tot);
                }
            }
        }
    }
    for(int pos=n*k;pos>=1;pos--){
        for(int t=1;t<=k;t++) printf("%d ",ans[pos][t]);
        printf("\n");
    }
    return 0;
}

推荐阅读