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;
}

posted @ 2025-03-14 06:52  programmingysx  阅读(103)  评论(0)    收藏  举报
Title