AGC055 题解
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\) 构成的字符串分 AB
和 BA
子序列。
仔细思考一下,可以如此贪心:
取最大的 \(k\),使得可以选出前 \(k\) 个 \(A\) 和后 \(k\) 个 \(B\) 组成一个好的子序列。
这样挑出来以后剩下的一定是 BB...BA..AA
,所以是正确的。
回到原题,不妨先将 ABC
和 ACB
两种一起考虑。将 \(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
-
定义一次操作为:选择任意一个为
ABC
,BCA
,CAB
之一的子串,将其替换为ABC
,BCA
,CAB
之一, -
判断能否通过有限次操作将 \(S\) 变为 \(T\)。
-
\(|S|=|T|=N\),\(1 \leq N \leq 5 \times 10^5\)。
全场最简单
为了方便,将位置为 \(i\) 的字母 \(c\) (\(c\) 为 \(0,1,2\) 之一)替换为 \((c-i) \bmod 3\)。这样 ABC
,BCA
,CAB
就变成了 AAA
,BBB
,CCC
。
观察一下样例,发现有一个很好的性质:
子串
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-t
个 XX
插入到 t+1
个 O
前面。用插板法,方案数为 \(\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)\) 算出每一个状态对答案的贡献。最后的答案为:
时间复杂度 \(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
-
称一个字符串为好的,当且仅当它能被拆分成若干子序列,使得每个子序列为
ABC
,BCA
,CAB
之一。 -
给定由
A
,B
,C
,?
组成的长度为 \(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
。只有
BCA
中B
在A
前面,所以肯定有 \(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}\),考虑分配每个球的步数。每个球是互相独立的,所以方案数就是可重排列:
只需要对所有 \(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 即可。
信息有点多,写在一起:
一个状态对答案的贡献:
下面来分析复杂度:
- 枚举 \(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;
}