[uoj76]懒癌
为了方便,称患有懒癌的狗为"坏狗"
记$Q_{i}$为第$i$个人能观察的狗集合,$S$为坏狗集合,那么第$k$天第$i$个人能得到的信息有且仅有$S\ne \empty$、$S\cap Q_{i}$、前$k-1$天没有开枪,而如果不存在$S_{0}$具有相同的信息且$i\not\in S_{0}$,那么其就会开枪
由此,令$T$为当前(第$k$天)仍未开枪的坏狗集合(的集合),初始$k=0$且$T$为全非空集,那么将$k$增加1后第$i$个人会在$S\in T$的状态下开枪当且仅当不存在$S_{0}\in T$满足$S\cap Q_{i}=S_{0}\cap Q_{i}$且$i\not\in S_{0}$
(特别的,若$i\not\in S$时一定不会开枪,因为取$S_{0}=S$即可)
由此暴力模拟,每一次判定复杂度为$o(n4^{n})$,并且若某次$T$不变显然之后也不会变,因此天数不超过$o(2^{n})$,总复杂度为$o(n8^{n})$
性质:(任意时刻)若$S\in T$,则$\forall S\subseteq S_{0},S_{0}\in T$
初始显然成立(仅有$\empty\not\in T$)
假设某个时刻第$i$个人在$S$的状态下开枪,即要将$S$在$T$中删除,归纳此性质即需要保证$\forall S'\subseteq S,S'\not\in T$(在该时刻之前已经被删除)或也在本次被删除
假设$S'\in T$且也未在本次被删除,即存在$S_{0}'\in T$满足$S'\cap Q_{i}=S'_{0}\cap Q_{i}$且$i\not\in S'$,进而可以根据归纳构造$S_{0}=S'_{0}\cup (S-S')\in T$,那么由于$S_{0}$显然第$i$个人同样不会在$S$的状态下开枪,矛盾
由此,第$i$个人会在$S$的状态下开枪当且仅当$S_{0}=(S\cup (U-Q_{i}))-\{i\}\not\in T$(其中$U$为全集),那么判定复杂度降为$o(n2^{n})$,总复杂度为$o(n4^{n})$
进一步的,对所有状态建图,从$(S\cup (U-Q_{i}))-\{i\}$向$S$连一条边,那么即求从$\empty$到$S$的最短路和(取到最短路时)最后一条边的方案数,直接bfs即可,总复杂度为$o(n2^{n})$
建立原来有向图的反图(即$i$到$j$有边当且仅当$j\ne i$且$j\not\in Q_{i}$),并将状态用染色来描述(将坏狗染为黑色,其余点为白色),那么$(S\cup (U-Q_{i}))-i$即将$i$变为白色、反图中$i$所到达的点变为黑色,而问题也即求这样最少要操作多少次能使得所有点均为白色和(取到最小值时)第一步的方案数
注意到只有操作的点会变为白色,那么若一个点存在一条以其为起点且任意长的路径:要使其变为白色一定要操作其而使得该路径上下个点为黑色,要使下个点变为白色一定要操作下个点而使得该路径上下下个点为黑色……最终显然无法使得所有点均为白色
不妨将这类点删除(递归判定即可),最终得到的即是一张DAG
而这个DAG上,操作次数的最小值显然即有多少个点存在一个黑点能到达其(包括自己),第一步的方案数即有多少个点满足其本身为黑点且不存在其他黑点能到达其
对每一个点去统计贡献,假设有$x$个点能到达其,那么其对两者的贡献即分别为$(2^{x}-1)2^{n-x}$和$2^{n-x}$
关于上述过程的实现,即是要维护连通性,用floyd+bitset即可
时间复杂度为$o(\frac{n^{3}}{\omega})$,可以通过
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define N 3005 4 #define mod 998244353 5 #define ll long long 6 bitset<N>f[N]; 7 int n,n0,ans1,ans2,mi[N],vis[N]; 8 char s[N]; 9 int main(){ 10 mi[0]=1; 11 for(int i=1;i<N;i++)mi[i]=2*mi[i-1]%mod; 12 scanf("%d",&n); 13 for(int i=1;i<=n;i++){ 14 scanf("%s",s+1); 15 for(int j=1;j<=n;j++) 16 if ((i!=j)&&(s[j]=='0'))f[i][j]=1; 17 } 18 for(int k=1;k<=n;k++) 19 for(int i=1;i<=n;i++) 20 if (f[i][k])f[i]|=f[k]; 21 for(int i=1;i<=n;i++) 22 if (!f[i][i])vis[i]=1; 23 for(int i=1;i<=n;i++) 24 for(int j=1;j<=n;j++) 25 if ((!vis[j])&&(f[i][j]))vis[i]=0; 26 for(int i=1;i<=n;i++) 27 if (vis[i])n0++; 28 for(int i=1;i<=n;i++) 29 if (vis[i]){ 30 int cnt=1; 31 for(int j=1;j<=n;j++) 32 if ((vis[j])&&(f[j][i]))cnt++; 33 ans1=(ans1+(ll)(mi[cnt]-1)*mi[n0-cnt])%mod; 34 ans2=(ans2+mi[n0-cnt])%mod; 35 } 36 printf("%d %d\n",ans1,ans2); 37 return 0; 38 }