【UR #6】懒癌

\(n\)个人,每个各有一条狗。至少一条狗有病。

人可以看到其它部分人的狗是否有病,通过有向图的形式给出,但看不出自己的狗是否有病。

然后有若干天:

  1. 上午:每个人都去看其它狗。
  2. 下午:如果存在人能推断出自己的狗有病,那么就把狗枪毙,操作结束(如果有多个人就同时做);否则继续下一天。

对于所有的生病情况,求:结束时间的和,被枪毙的狗数量和。

\(n\le 3000\)


神仙题。

表示自己想了半天想到了个和题解无关的复杂的大模拟,当然时间复杂度很劣……

考虑一个人怎么知道自己的狗有病:那么他先假设自己的狗没病,据此推断出别人的行动是否合理。设\(f_S\)表示生病狗集合,此时结束时间。转移:枚举\(i\in S\),从\(i\)的角度看:能看到的狗的状态固定,假设\(i\)自己没病,剩下的狗枚举状态。对于所有的这些后继状态\(T\),计算\(\max_T f_T\)。于是\(f_S=1+\min_S\max_T f_T\)

建立一个模型来模拟DP过程:建出原来有向图的补图。一个状态相当于给这个图上一些点染黑。转移可以视作:选择一个黑点,将其变白,对于它的所有后继,决定是否将其染黑。终止状态为所有点变白。

首先可证:在这张图上,如果存在黑点能到达环,那么无解。可以构造:钦定对后继是否染黑时,钦定全部染黑。此时显然怎么转移都无法到达终止状态。

于是把所有能到达环的点删掉,剩下就是个DAG。

然后可证:如果\(S\subseteq T\)\(f(S)\le f(T)\)。因为此时的状态转移也是个DAG,所以可以归纳了。

于是每次转移都直接钦定对后继全部染黑。那么转移中取\(\max\)已经解决,只剩下取\(\min\)

显然可以贪心:按照拓扑序来,如果当前点为黑,那么就选择这个点进行转移。此时,结束时间就是所有黑点能够到达的点的集合大小。算出\(p_i\)表示图中能到达\(i\)的点数,第一个答案为\(\sum_i (2^{p_i}-1)2^{n'-p_i}\)

被枪毙的狗的数量:即第一步选择转移之后,\(f_T\)为最小值的个数。考虑怎样转移是最优的,那么第一步转移的一定是 不存在能到达它的黑点 的点。于是第二个答案为\(\sum_i 2^{n'-p_i}\)

用bitset优化,时间\(O(\frac{n^3}{\omega})\)


using namespace std;
#include <bits/stdc++.h>
#define N 3005
#define mo 998244353
#define ll long long
int n;
ll pw[N];
int e[N][N];
void read(){
	scanf("%d",&n);
	for (int i=0;i<n;++i){
		static char str[N];
		scanf("%s",str);
		for (int j=0;j<n;++j)
			e[i][j]=str[j]-'0'^1;
		e[i][i]=0;
	}
}
int dfn[N],low[N],tim;
int st[N],tp,ins[N];
bool del[N];
void tarjan(int x){
	low[x]=dfn[x]=++tim;
	st[++tp]=x,ins[x]=1;
	int tmp=tp;
	for (int y=0;y<n;++y)
		if (e[x][y]){
			if (!dfn[y])
				tarjan(y),low[x]=min(low[x],low[y]);
			else if (ins[y])
				low[x]=min(low[x],dfn[y]);
			del[x]|=del[y];
		}
	if (low[x]==dfn[x]){
		bool bz=(tp-tmp+1>1);
		for (int i=tmp;i<=tp;++i) bz|=del[st[i]],ins[st[i]]=0;
		if (bz)
			for (int i=tmp;i<=tp;++i) del[st[i]]=1;
		tp=tmp-1;
	}
}
int m,p[N];
int deg[N];
queue<int> q;
bitset<N> b[N];
void BFS(){
	for (int i=0;i<n;++i)
		if (!del[i]){
			++m;
			for (int j=0;j<n;++j)
				if (!del[j] && e[i][j])
					deg[j]++;
		}
	for (int i=0;i<n;++i)
		if (!del[i]){
			b[i][i]=1;
			if (!deg[i])
				q.push(i);
		}
	while (!q.empty()){
		int x=q.front();
		q.pop();
		for (int y=0;y<n;++y)	
			if (!del[y] && e[x][y]){
				b[y]|=b[x];
				if (!--deg[y])
					q.push(y);
			}
	}
	for (int i=0;i<n;++i)
		p[i]=b[i].count();
}
int main(){
	read();
	for (int i=0;i<n;++i)
		if (!dfn[i])
			tarjan(i);
	BFS();
	pw[0]=1;
	for (int i=1;i<=n;++i)
		pw[i]=pw[i-1]*2%mo;
	ll ans1=0,ans2=0;
	for (int i=0;i<n;++i)
		if (!del[i]){
			(ans1+=(pw[p[i]]-1)*pw[m-p[i]])%=mo;
			(ans2+=pw[m-p[i]])%=mo;
		}
	printf("%lld %lld\n",ans1,ans2);
	return 0;
}
posted @ 2021-06-10 11:53  jz_597  阅读(62)  评论(0编辑  收藏  举报