Atcoder 记录
Atcoder 记录
ARC193C Grid Coloring 3
题面:
给你一个 \(n\times m\) 的网格初始全没有被染色,每次可以选择 \((x,y,c),1\leq x\leq n,1\leq y\leq m,1\leq c\leq C\) 将,第 \(x\) 行和第 \(y\) 列全部染成第 \(c\) 种颜色(覆盖之前的颜色),可以操作任意次,问最终有多少种不同的染色方案。\(n,m<=400,C<=1e9\)
题解:
因为后面染色能覆盖前面的颜色,所以要将操作序列时光倒流,现在变成每次染还没有颜色的格子,有多少种方案。
在第一次操作(翻转操作序列前的最后一次操作)染了一个十字之后,钦定后面的每一次操作中心 \((x,y)\) 必须已经被染过色,那第 \(x\) 行和第 \(y\) 列中必有一个已经全部染色了,那么操作就变成了每次给一行或者一列染一个颜色(分别记为 \(A,B\) 操作),显然最终的答案是不变的。
但这时直接枚举每次操作是啥仍然不太可做,原因是对于操作序列中两次连续的 \(A\) 操作或 \(B\) 操作,交换他们的操作顺序不影响答案,所以会算重。由于 \(A\) 和 \(A\) 之间互不干扰,\(B\) 和 \(B\) 之间也是,这启发我们每次可以同时操作操作序列中一段极长的 \(A\) 或 \(B\),显然这样转化后答案仍然是对的,这就好做了。
将第一段特殊处理,枚举最终有 \(i\) 行 \(j\) 列都是第一次操作的颜色,方案数是 \(C\binom{n}{i}\binom{m}{j}\),然后进行 \(dp\)。
先设一个大概的雏形 \(f_{n,m,* }\) 表示给 \(n\times m\) 的网格染色的答案。
在此之后的下一个极长段中,显然不能有一行或一列的颜色和第一次操作的颜色一样,于是我们将 \(dp\) 改成 \(f_{n,m,t,* }\) 表示有 \(t\) 中颜色不能作为一行或一列全部相同时的颜色,显然 \(t\in\{0,1\}\)。
考虑下一个极长段,不妨设其是 \(A\) 操作的极长段,设此次操作了 \(r\) 行,方案数是 \((C-t)^r\),后续操作中不再有 \(t\) 的限制,所以从 \(f_{n-r,m,0,* }\) 转移过来。
但是为了保证这一段 \(A\) 是极长的,要求剩下的 \(n-r\) 行中不能有某一行的颜色全部相同,列同理,所以要记 \(f_{n,m,t,x,y}\),\(x,y\) 分别表示当前是否限定每一行(列)的颜色不能完全相同。发现 \(x,y\) 两个限制最多同时存在一个(如果同时存在两个的话就没有能操作的东西了)所以我们可以只记一个(下面只记列是否有限制),通过交换行列数来限定是行还是列,例如上边的转移被写作 \(f_{n,m,t,0}\leftarrow f_{m,n-r,0,1}\)。
然后是 \(f_{n,m,t,1}\) 的转移,要求是没有一列的颜色全相同。分两类讨论,如果这 \(r\) 行本身的颜色就有不同的,肯定符合要求,方案数是 \((C-t)^r-(C-t)\),从 \(f_{m,n-r,0,1}\) 转移过来。如果这 \(r\) 行都相同,那要求这列其余部分不能全都是当前的这种颜色,这个状态就是 \(f_{m,n-r,1,1}\) 的限制,方案数是 \((C-t)\)。
最后答案是 \(f_{n-i,m-j,1,0}+f_{m-j,n-i,1,1}\) 即考虑执行完最终第一次操作的那些之后的第一个极长段时 \(A\) 还是 \(B\)。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read(){
ll s=0,k=1;
char c=getchar();
while(c>'9'||c<'0'){
if(c=='-') k=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
s=(s<<3)+(s<<1)+(c^48);
c=getchar();
}
return s*k;
}
const int N=405,mod=998244353;
ll fac[2][N],f[N][N][2][2],C[N][N],K;
int n,m;
void Add(ll &x,ll y){
x+=y;
if(x>=mod) x-=mod;
}
ll dfs(int n,int m,int t,int c){
if(f[n][m][t][c]>=0) return f[n][m][t][c];
ll res=fac[t][n];
if(c) Add(res,mod-(K-t));
for(int i=1;i<n;i++){
ll tmp=0;
if(!c) Add(tmp,fac[t][i]*dfs(m,n-i,0,1)%mod);
else{
Add(tmp,(K-t)*dfs(m,n-i,1,1)%mod);
Add(tmp,(fac[t][i]-(K-t)+mod)%mod*dfs(m,n-i,0,1)%mod);
}
Add(res,tmp*C[n][i]%mod);
}
return f[n][m][t][c]=res;
}
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n=read();m=read();K=read();
int S=max(n,m);
K%=mod;
if(K==0) K=mod;
for(int i=0;i<=S;i++){
C[i][0]=1;
for(int j=1;j<=i;j++){
C[i][j]=C[i-1][j];
Add(C[i][j],C[i-1][j-1]);
}
}
fac[0][0]=fac[1][0]=1;
for(int i=1;i<=S;i++){
fac[0][i]=fac[0][i-1]*K%mod;
fac[1][i]=fac[1][i-1]*(K-1)%mod;
}
memset(f,-1,sizeof(f));
ll ans=1;
for(int i=1;i<n;i++)
for(int j=1;j<m;j++)
Add(ans,C[n][i]*C[m][j]%mod*(dfs(n-i,m-j,1,0)+dfs(m-j,n-i,1,1))%mod);
(ans*=K)%=mod;
printf("%lld",ans);
return 0;
}
ARC193D Magnets
题面:
给两个长为 \(n\) 的 \(01\) 序列 \(A,B\),每次可以选择一个位置让每个 \(1\) 都向选的位置靠近一位,若一个位置上有多个 \(1\) 认为是一个。问最少多少次操作能让 \(A\) 序列和 \(B\) 序列一样,或报告无解。\(n\leq 1e6\)
题解:
观察操作发现,如果我们操作在所有 \(1\) 前或所有 \(1\) 后,可以起到一个平移的效果,即 \(A\) 中所有 \(1\) 的相对位置不变,这启示我们算出 \(A,B\) 的差分数组 \(da,db\)。问题转化为要把 \(A\) 的差分数组变成和 \(B\) 的差分数组一样(删去所有 \(da_i=0\))。
考虑一次操作会导致 \(da\) 发生什么变化:如果操作在两个 \(1\) 中间,会导致 \(da_i-2\)(称为 \(X_i\) 操作);如果操作在某个 \(1\) 上,会导致 \(da_i,da_{i+1}\) 都减少 \(1\)(称为 \(Y_i\) 操作)。特别的,如果操作在第一个或最后一个 \(1\) 上,只会令 \(da_1-1\) 或 \(da_m-1\)。
先不考虑这两个特殊的操作,那么每次操作都会让 \(da\) 的总和 \(-2\),操作次数的总和就是 \(\frac{\sum da_i-\sum db_i}{2}\)。我们让 \(da_i\) 依次和 \(db_j\) 匹配,如果 \(da_i>db_j\) 那么我们一直 \(da_i-2\) 直到 \(da_i==db_j+1\) 我们让 \(da_i-1,da_{i+1}-1\),然后匹配 \(da_{i+1},db_{j+1}\),否则如果 \(da_i<db_j\) 就把 \(da_i\) 减成零。如果最后 \(db_j\) 有剩下的没有配上或者 \(da_{m+1}\) 被 \(-1\) 了就不合法,我们就需要通过特殊操做尽量让他合法并保证次数最小。
要求最小次数,那我们只要执行尽量少的特殊操作就行了,因为别的操作都是 \(-2\) 而特殊操作是 \(-1\)。
有结论,特殊操做只会在开头和结尾各最多做一次。
如果最开始一下做了两次特殊操作(不妨设是在开头做的),那么可以将其改为一个 \(X\) 操作并向左平移一位,这样更改后没有变化操作数,连绝对位置都没有变化,可以替换。
如果匹配了某个 \(da\) 和 \(db\) 的前缀后 \(da_i\) 现在成了队头并对其做了一次特殊操作,那么考虑对 \(da_{i-1}\) 执行的操作,如果执行了 \(X_{i-1}\),可以替换为 \(Y_{i-1}\) 和对 \(i-1\) 是队头时的特殊操作,如果执行了 \(Y\) 操作类似,这样我们就把 \(i\) 这个位置的特殊操作前移了,以此类推,我们可以全部都移到最开始的位置做。
所以这题的做法就是枚举首尾是否进行特殊操作,然后按上述方式检测是否合法,同时统计操作次数。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read(){
ll s=0,k=1;
char c=getchar();
while(c>'9'||c<'0'){
if(c=='-') k=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
s=(s<<3)+(s<<1)+(c^48);
c=getchar();
}
return s*k;
}
const int N=1e6+5,inf=1e9+7;
int n;
char s[N],t[N];
int solve(int pl,int pr,vector<int>A,vector<int>B){
if(pl) for(int i=1;i<A.size();i++) A[i]--;
if(pr) for(int i=0;i<A.size()-1;i++) A[i]++;
vector<int>dx,dy;
for(int i=1;i<A.size();i++)
if(A[i]>A[i-1]) dx.push_back(A[i]-A[i-1]);
for(int i=1;i<B.size();i++)
if(B[i]>B[i-1]) dy.push_back(B[i]-B[i-1]);
if(dy.size()>dx.size()) return inf;
int j=0,op=0,ans=0;
for(int i=0;i<dx.size();i++){
dx[i]-=op;
int d=dx[i];
if(j<dy.size()&&dx[i]>=dy[j]){
d-=dy[j];
j++;
}
ans+=d+1>>1;
op=d&1;
}
if(j<dy.size()||op) return inf;
return ans+abs(A[0]+ans-B[0])+pl+pr;
}
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
int T=read();
while(T--){
n=read();
scanf("%s",s+1);
scanf("%s",t+1);
vector<int>A,B;
for(int i=1;i<=n;i++){
if(s[i]=='1') A.push_back(i);
if(t[i]=='1') B.push_back(i);
}
if(B.size()>A.size()){
puts("-1");
continue;
}
if(A.size()==1){
printf("%d\n",abs(A[0]-B[0]));
continue;
}
int ans=inf;
for(int i=0;i<2;i++)
for(int j=0;j<2;j++)
ans=min(ans,solve(i,j,A,B));
if(ans>inf/2) puts("-1");
else printf("%d\n",ans);
}
return 0;
}
ARC195E Random Tree Distance
题面:
给定一个长为 \(n-1\) 的序列 \(a_2\sim a_n\),对于一个长为 \(n-1\) 的满足 \(p_i<i\) 的序列 \(p_2\sim p_n\),以 \(p_i\) 为 \(i\) 的父亲 \(a_i\) 为 \(i\) 和 \(p_i\) 之间的边权,构造一棵以 \(1\) 为根的树。
\(Q\) 次询问,每次给出一个 \((u,v)\),问对于所有合法的序列 \(p\),以如上方式构造一棵树,树上 \(u\to v\) 的距离的和是多少。
\(n,Q\leq 2\times 10^5,1\leq a_i\leq 10^9\)
题解:
考虑转成期望计数,显然所有合法的序列 \(p\) 的方案数是 \((n-1)!\)。
两点间距离转换成 \(dis(u,v)=dep_u+dep_v-2\times dep_{lca(u,v)}\)。
设 \(E_x\) 为 \(dep_x\) 的期望深度,有转移方程 \(E_x=a_x+\frac{1}{x-1}\sum_{i=1}^{x-1}E_i\)。
然后求 \(dep_{lca(u,v)}\) 的期望,不妨设 \(u<v\),则一定有 \(lca(u,v)<u\),考虑暴力跳 \(lca\) 的过程,若 \(p_v>u\) 则和最开始的情况没有区别,若 \(p_v<u\) 则这次跳之后的落点在 \([1,v-1]\) 的概率相等,即发现 \(lca\) 在 \([1,u]\) 之内的概率是相等的。
因此有结论 \(\forall j_1,j_2>i,E(dep_{lca(i,j_1)})=E(dep_{lca(i,j_2)})\),所以 \(E(dep_{lca(u,v)})\) 与 \(v\) 无关。
设 \(f_i\) 表示 \(E(dep_{lca(i,*)}),*>i\),当 \(lca(i,*)=i\) 时,贡献是 \(E_i\),否则若 \(lca(i,*)=j,j<i\),贡献是 \(f_j\)。所以有转移 \(f_i=\frac{1}{i}(E_i+\sum_{j=1}^{i-1}f_j)\)。
最终答案是 \((n-1)!\times (E_u+E_v-2\times f_{\min(u,v)})\)。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read(){
ll s=0,k=1;
char c=getchar();
while(c>'9'||c<'0'){
if(c=='-') k=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
s=(s<<3)+(s<<1)+(c^48);
c=getchar();
}
return s*k;
}
const int N=2e5+5,mod=998244353;
int n,Q;
ll inv[N],a[N],f[N],g[N];
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n=read();Q=read();
inv[0]=inv[1]=1;
for(int i=2;i<=n;i++) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
for(int i=2;i<=n;i++) a[i]=read();
ll sum=0;
for(int i=2;i<=n;i++){
f[i]=(sum*inv[i-1]+a[i])%mod;
(sum+=f[i])%=mod;
}
sum=0;
for(int i=2;i<=n;i++){
g[i]=inv[i]*(sum+f[i])%mod;
(sum+=g[i])%=mod;
}
ll fac=1;
for(int i=1;i<n;i++) (fac*=i)%=mod;
for(int i=1;i<=Q;i++){
int x=read(),y=read();
if(x>y) swap(x,y);
printf("%lld\n",fac*(f[x]+f[y]-g[x]*2%mod+mod)%mod);
}
return 0;
}
ARC196D Roadway
题面:
\(n\) 个点排成一条线,由 \(n-1\) 条边顺次连接。有 \(m\) 个人,每个人从 \(s_i\) 走到 \(t_i\),初始其体力为 \(0\),经过一条边权为 \(w_i\) 的边后体力会加上 \(w_i\)。每个人都想使得走到 \(t_i\) 时体力是 \(0\) 且每个中间点的体力都是正整数。
\(Q\) 次询问,每次问只考虑 \([L_i,R_i]\) 的人,是否有一种给边赋权的方案使得满足每个人的要求。
\(n,m,Q\leq 4\times 10^5,\ \ |s_i-t_i|>1,\ \ (s_i,t_i)\not= (s_j,t_j)(i\not= j)\) 注意不保证 $s_i<t_i $。
题解:
考虑将每个点设一个势能 \(v_j\),连接 \(i,i+1\) 的边权 \(w_i=v_{i+1}-v_i\)。那么若一个人 \(s_i<t_i\),其限制转化为 \(v_{s_i}=v_{t_i},\forall j\in (s_i,t_i),v_j>v_{s_i}\)。如果 \(s_i>t_i\) 就是 \(v_j<v_{s_i}\)。
那如果只有 \(s_i<t_i\) 怎么做?每个 \((s_i,t_i)\) 不能有相交的边,可以包含。形式化的说,对于每个 \((i,j)\) 都要满足以下条件之一:
- \(t_i\leq s_j\) 或 \(t_j\leq s_i\)
- \(s_i<s_j<t_j<t_i\) 或 \(s_j<s_i<t_i<t_j\)
我们称其为条件 \((*)\)。
若有一对 \((i,j)\) 不满足 \((*)\),不妨设他们端点从左到右是 \(s_i,s_j,t_i,t_j\) 那根据限制有 \(s_j>s_i=t_i>s_j\to s_j>s_j\),是不合法的。
若都满足 \((*)\),则相离的区间互不干扰,包含的区间大小关系是确定的,所以一定存在解。
如果同时有 \(s_i<t_i\) 和 \(s_j>t_j\):
- 只考虑 \(s_i<t_i\) 的人要满足 \((*)\)。
- 只考虑 \(s_i>t_i\) 的人要满足 \((*)\)。
- 首先对于任意不同方向的 \((i,j)\) 有 \(s_i\not= t_j\) 且 \(s_j\not= t_i\)。
否则在两区间相等的边缘处的势能一定无法满足。
例如说 \(s_i<t_i,s_j>t_j,s_i=t_j\) 设 \(x=s_i=t_j\),那 \(i\) 要求 \(v_{x+1}>v_x\),\(j\) 要求 \(v_{x+1}<v_x\),这是矛盾的。
这三个条件都是合法的必要条件,而他们三个加起来也是充要的。
考虑一个 \(n\) 个点 \(m\) 条边的有向图,每条边从 \(s_i\to t_i\)。
对于一个 \(s_i<x<t_i\) 的点 \(x\) 我们称 \(x\) 被这条边覆盖。
如果暂时的把每条边看做无向边,则这个图可以切割成若干联通块,每个联通块内的连边一定形如 \(a\gets \cdots \gets d\gets x \to e \to \cdots \to g\)(可能不存在向左或向右的延伸)。我们称它为一条路径。一条路径的最左顶点为 \(l\),最右为 \(r\)。
\([l,r]\) 不交的两条路径其势能之间没有限制。
考虑相交的两条路径 \((i,j)\),设 \(l_i<l_j<r_i<r_j\),如果 \(i\) 中覆盖 \(l_j\) 的那条边 \(k\) 满足 \(s_k<t_k\) 则 \(i,j\) 的大小关系是 \(i<j\),否则是 \(i>j\)。
假设是 \(i<j\),那么对于 \(j\) 覆盖的某个点 \(x\),若覆盖 \(x\) 的那条边是 \(s_p<x<t_p\) 的,那么根据上述条件的限制,\(i\) 中覆盖这个点的边肯定是 \(s_q<s_p<x<t_p<t_q\),符合 \(i<j\) 的关系,反之亦然。
所以各个路径之间的大小关系可以的到一个 \(DAG\),并以此确定解。
在判断合法时第三个条件容易检查,第一二个条件可以使用 \(bit\) 维护 \(xor-hashing\) 来解决,具体可见代码。
双指针的对于每个 \(r=1\sim i\) 处理最远的合法左端点 \(f_r\),在询问时判断是否 \(l\geq f_r\) 即可。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read(){
ll s=0,k=1;
char c=getchar();
while(c>'9'||c<'0'){
if(c=='-') k=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
s=(s<<3)+(s<<1)+(c^48);
c=getchar();
}
return s*k;
}
const int N=4e5+5;
mt19937 rnd(time(0));
int n,m,Q;
struct Tree{
ll c[N];
void add(int x,ll v){
while(x<=n){
c[x]^=v;
x+=x&-x;
}
}
ll ask(int x){
ll res=0;
while(x>0){
res^=c[x];
x-=x&-x;
}
return res;
}
}tr[2];
int L[N],R[N],l[N],r[N],op[N],mn[N];
ll val[N];
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n=read();m=read();Q=read();
for(int i=1;i<=m;i++){
l[i]=read(),r[i]=read();
op[i]=l[i]>r[i];
if(l[i]>r[i]) swap(l[i],r[i]);
}
for(int i=1;i<=m;i++) val[i]=rnd();
for(int i=1,j=1;i<=m;i++){
tr[op[i]].add(l[i],val[i]);
tr[op[i]].add(r[i],val[i]);
L[l[i]]++;R[r[i]]++;
while(L[l[i]]>1||R[r[i]]>1||tr[op[i]].ask(r[i]-1)!=tr[op[i]].ask(l[i])){
tr[op[j]].add(l[j],val[j]);
tr[op[j]].add(r[j],val[j]);
L[l[j]]--; R[r[j]]--;
j++;
}
mn[i]=j;
}
while(Q--){
int l=read(),r=read();
if(l>=mn[r]) puts("Yes");
else puts("No");
}
return 0;
}
ARC197D Ancestor Relation
题面:
给定一个 \(n\times n\) 的 \(01\) 矩阵 \(a\),如果 \(a_{i,j}\) 是 \(1\) 表示在以 \(1\) 为根的树中 \(i,j\) 有祖先后代关系,反之则没有。问符合条件的树的方案数。\(n\leq 400\)
题解:
首先将一张图 \(G\),若 \(a_{i,j}=1\) 则 \(i,j\) 之间有边,思考一种映射方式将这个图映射到一棵树。
考虑当前的树根 \(x\) 要满足的条件是 \(x\) 跟其连通块中的所有点都有连边,方案数就乘上符合这个条件的点的个数。
如果有多个符合条件的点可以任选一个当根,连通性不变,然后递归到新产生的所有连通块中处理。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read(){
ll s=0,k=1;
char c=getchar();
while(c>'9'||c<'0'){
if(c=='-') k=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
s=(s<<3)+(s<<1)+(c^48);
c=getchar();
}
return s*k;
}
const int N=405,mod=998244353;
int n,a[N][N];
ll ans=0;
bool vis[N];
vector<int> bfs(int s){
vector<int>vec;
queue<int>q;
q.push(s);vis[s]=1;
vec.push_back(s);
while(!q.empty()){
int x=q.front();
q.pop();
for(int v=1;v<=n;v++)
if(a[x][v]&&!vis[v]){
vis[v]=1;
q.push(v);
vec.push_back(v);
}
}
return vec;
}
void dfs(vector<int>vec){
if(vec.size()==1) return ;
int cnt=0,id=0;
for(int x:vec){
bool fl=1;
for(int y:vec) fl&=a[x][y];
if(fl) cnt++,id=x;
}
(ans*=cnt)%=mod;
if(!cnt) return ;
for(int i=1;i<=n;i++) a[id][i]=a[i][id]=0;
for(int x:vec) vis[x]=0;
vector<vector<int> >tmp;
for(int x:vec)
if(x!=id&&!vis[x]) tmp.push_back(bfs(x));
for(auto x:tmp) dfs(x);
}
void solve(){
n=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++) a[i][j]=read();
for(int i=1;i<=n;i++) vis[i]=0;
vector<int>vec=bfs(1);
if(vec.size()!=n){
puts("0");
return;
}
for(int i=1;i<=n;i++)
if(!a[i][1]){
puts("0");
return ;
}
ans=1;
for(int i=1;i<=n;i++) a[i][1]=a[1][i]=0;
for(int i=1;i<=n;i++) vis[i]=0;
vector<vector<int> >tmp;
for(int i=2;i<=n;i++) if(!vis[i]) tmp.push_back(bfs(i));
for(auto x:tmp) dfs(x);
printf("%lld\n",ans);
}
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
int T=read();
while(T--) solve();
return 0;
}
ARC197E Four Square Tiles
题面:
有一个 \(n\times m\) 的网格,要在网格上放 \(4\) 个 \(k\times k\) 的矩形,任意两个矩形不能有交,问方案数。\(n,m,k\leq 10^9,n,m\leq 3k-1\)
题解:
根据题目限制,没有一行或一列可以放下三个矩形,所以四个矩形一定是左上右上左下右下,设他们的左上角坐标为 \((x_i,y_i)\)。
考虑行或列相邻的两个不交,有不等式:
- \(1\leq x_1,x_1+k\leq x_3,x_3+k-1\leq n\)
- \(1\leq x_2,x_2+k\leq x_4,x_4+k-1\leq n\)
- \(1\leq y_1,y_1+k\leq y_3,y_3+k-1\leq n\)
- \(1\leq y_2,y_2+k\leq y_4,y_4+k-1\leq n\)
化简得:
- \(1\leq x_1<x_3-k+1\leq n-2k+2\)
- \(1\leq x_2<x_4-k+1\leq n-2k+2\)
- \(1\leq y_1<y_3-k+1\leq n-2k+2\)
- \(1\leq y_2<y_4-k+1\leq n-2k+2\)
方案数是 \(\binom{n-2k+2}{2}^2\binom{m-2k+2}{2}^2\)。
容斥掉不合法的情况就是对角的两个矩阵有交,显然不可能两对同时有交,所以只需要算一对有交的方案,另一对方案数是一样的。
那么有交的条件就是:
- \(1\leq x_1,x_1+k\leq x_3,x_3+k-1\leq n\)
- \(1\leq x_2,x_2+k\leq x_4,x_4+k-1\leq n\)
- \(1\leq y_1,y_1+k\leq y_3,y_3+k-1\leq n\)
- \(1\leq y_2,y_2+k\leq y_4,y_4+k-1\leq n\)
- \(x_4<x_1+k,y_4<y_1+k\)
化简得:
- \(1\leq x_2 < x_4-k+1<x_1+1<x_3-k+2\leq n-2k+3\)
- \(1\leq y_2 < y_4-k+1<y_1+1<y_3-k+2\leq m-2k+3\)
方案数是 \(\binom{n-2k+3}{4}\binom{m-2k+3}{4}\)。
所以最终答案为 \(\binom{n-2k+2}{2}^2\binom{m-2k+2}{2}^2-2\binom{n-2k+3}{4}\binom{m-2k+3}{4}\)。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read(){
ll s=0,k=1;
char c=getchar();
while(c>'9'||c<'0'){
if(c=='-') k=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
s=(s<<3)+(s<<1)+(c^48);
c=getchar();
}
return s*k;
}
const int mod=998244353;
ll n,m,K;
ll ksm(ll a,ll b){
ll t=1;
for(;b;b>>=1,a=a*a%mod)
if(b&1) t=t*a%mod;
return t;
}
ll C(ll n,ll m){
if(n<m||n<0||m<0) return 0;
ll res=1;
for(int i=n-m+1;i<=n;i++) (res*=i)%=mod;
for(int i=1;i<=m;i++) (res*=ksm(i,mod-2))%=mod;
return res;
}
ll Pow(ll x){
return x*x%mod;
}
ll Mod(ll x){
if(x>=mod) x-=mod;
return x;
}
void solve(){
K=read();n=read();m=read();
if(n<K<<1||m<K<<1) return puts("0"),void();
printf("%lld\n ",Mod(Pow(C(n-K*2+2,2)*C(m-K*2+2,2)%mod)+mod-Mod(C(n-K*2+3,4)*C(m-K*2+3,4)%mod*2)));
}
int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
int T=read();
while(T--) solve();
return 0;
}