专栏文章

题解:AT_arc197_d [ARC197D] Ancestor Relation

AT_arc197_d题解参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@mimyzc0r
此快照首次捕获于
2025/12/01 17:51
3 个月前
此快照最后确认于
2025/12/01 17:51
3 个月前
查看原文

思路

好题要赞,建议评蓝。

题目描述


给你一个 N×NN\times N 的矩阵 AA,其中每一项只会是 0011
请找出有多少种不同的 NN 个点的树 GG,使得:
1i,jN,Ai,j=1\forall 1\le i,j\le N,A_{i,j}=1,当且仅当 i=ji=j,或以结点 11 为根时,iijj 的祖先或 jjii 的祖先。
两棵树不同,当且仅当存在两个结点 i,ji,j,在其中一棵树上它们之间有直接连边而在另一棵树上没有。
请输出答案对 998244353998244353 取模后的结果。

这题显而易见的位运算。
我们令 N×NN \times N 的矩阵为 Ai,jA_{i,j},则不妨用一个 bitset\text{bitset} 表示每一行中 aja_j0011 总体情况。我们令这个 bitset\text{bitset}sis_i
更具题目定义,我们有若 ai,j=1ija_{i,j}=1(i \not = j),则 iijj 为祖先关系,即要么 iijj 的祖先,要么 jjii 的祖先。所以 iijj 同链。因为题目说 11 为根,则 i[1,N]ai,1=a1,i=1\forall i \in [1,N],a_{i,1}=a_{1,i}=1。可以判掉一部分无解情况。
定义 iijj 有关系当且仅当 ai,j=aj,i=1a_{i,j}=a_{j,i}=1
我们有如下结论:
结论 11:与子树内节点有关系的节点一定与祖先有关系。
结论 22:与祖先有关系的节点不一定与子树内节点有关系。
证明:
与子树内节点的节点一定在子树内的点到根节点 11 的唯一路径上(唯一链),而祖先一定处于这个位置(祖先的定义),所以结论 11 成立。
同理,我们可以举出“与祖先有关系的节点一定与子树内节点有关系。”的反例。例如,我们设祖先为 kk,若 kk 节点分叉,随意选 22 条链即可举出反例。
所以我们有公式,一下钦定 ii 处于 jj 上面:
  • ai,j=aj,i=1a_{i,j}=a_{j,i}=1,则 sis_i 一定包含 sjs_j,即 sisj=sis_i | s_j =s_i
  • ai,j=aj,i=0a_{i,j}=a_{j,i}=0,则一定不存在 sisj=sis_i | s_j =s_i
同时还有 si=sjs_i=s_j 的情况。则可以再次判掉剩下的无解情况。
接下来考虑有解情况。根据上面的推导,我们发现交换 iijj 的位置不会改变情况,假设链上有 cnt\text{cnt} 个,很明显排列组合一下情况数有 cnt!cnt!
所以预处理一下阶乘,在算一下即可。
bitset\text{bitset} 维护做到了 O(n3w)O(\frac{n^3}{w})。可以尝试推导一下时间复杂度。

代码

CPP
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=405;
const int mod=998244353;
int t,n,jc[N];
bool vis[N];
signed main(){
	jc[0]=1;
	for(int i=1;i<=400;i++) jc[i]=jc[i-1]*i%mod;
	scanf("%lld",&t);
	while(t--){
		scanf("%lld",&n);
		memset(vis,0,sizeof(vis));
		bitset <N> s[N];
		memset(vis,0,sizeof(vis));
		for(int i=1;i<=n;i++)
			for(int j=1,x;j<=n;j++)
				scanf("%lld",&x),s[i][j]=x;
		bool f=0;
		for(int i=1;i<=n;i++)
			if(!s[i][1]||!s[1][i]){f=1;break;}
		if(f==1){printf("0\n");continue;}
		for(int i=2;i<=n;i++){
			for(int j=2;j<=n;j++)
				if((((s[i]|s[j])==s[i])||((s[i]|s[j])==s[j]))&&!s[i][j]){f=1;break;}
				else if((s[i]|s[j])!=s[i]&&(s[i]|s[j])!=s[j]&&s[i][j]){f=1;break;}
		    if(f==1) break;
		}
		if(f==1){printf("0\n");continue;}
		int ans=1;
		for(int i=2;i<=n;i++)
			if(!vis[i]){
				int cnt=1;
				for(int j=i+1;j<=n;j++) 
					if(s[i]==s[j]) cnt++,vis[j]=1;
				ans=ans*jc[cnt]%mod;
			}
		printf("%lld\n",ans);
	}
	return 0;
}

撒花!

评论

0 条评论,欢迎与作者交流。

正在加载评论...