#76. 【UR #6】懒癌
题目大意
完整题目: http://uoj.ac/problem/76
n只狗n个人,有至少一只病狗,每个人能看出其他一些人的狗是否是病狗(成一个有向图)
每天早上所有人会集会,如果一个人判断出了自家的狗是病狗下午就会把狗毙了,当听到枪声之后所有人都不再集会
已知全部人都绝顶聪明且知道有至少一只病狗以及知道具体的有向图,并且每个人都知道彼此知道前面←那些东西
问\(2^n-1\)种病狗的情况中开枪时间之和及总死亡狗的数目
题解
人类智慧神仙题
假设每个人都能看到除自己外的所有人,那么这是个经典问题
设有x只病狗,若x=1则病狗的主人会在第一天早上发现除自己外没有病狗就会在第一天下午开枪
换一种方法考虑,病狗的主人可以假设自己这条狗不是病狗,则剩余0条病狗,与条件矛盾,因此自己的狗是病狗
把假设法推广一下,当x=2时其中的一个病狗主人假设自己的狗不是病狗,则第一天下午会有枪声
但是第一天下午没有枪声,所以他判断出自己的狗是病狗并在第二天下午开枪
x=3时则假设第二天开枪,发现没有后在第三天开枪
由此类推,全部 病狗的主人会在第x天开枪
当加上了有向图后仍可以用假设法
把有向图取反,一个人会在max(假设自己的狗不是病狗后的任意一种情况的开枪时间)+1天开枪
感受一下,病狗显然是越多越会干扰判断,因此一个病狗集合的开枪时间必然不小于集合的子集
因此每个人会在第(假设自己的狗不是病狗且看不到的都是病狗的开枪时间+1)天开枪
设病狗在图上为黑点,则相当于每次把一个黑点变白,把其在反图上有边的点变黑,然后时间+1
可以发现,如果一个黑点能走到一个环,那么永远也不可能会开枪,因为会一直传递下去(本质是信息不能流通)
拓扑一下可以得到若干dag,再感受一下发现开枪的时间就等于把黑点不断向下扩散所覆盖的总点数
假设每次都从没有能走到其的点开始扩散,总时间就等于覆盖的点数,而这就是最小时间
因为如果扩散的是一个有能走到它的点,无论怎样扩散最后还要考虑先前↖的点,时间必然更大
这样也得到了另一个结论:一只狗会被毙掉当且仅当没有其他黑点能走到它
于是就很好办了,拓扑找dag,bitset维护能走到某个点的点集
开枪时间=覆盖点数,因此一个点会被算入当且仅当有至少一个点能走到它(包括自己),其余点任意
狗会死亡只有 没有除自己外的点能到自己 时才会被算入
时间复杂度:\(O(\frac{n^3}{32})\)
讲个笑话:如何评价一道拓扑就能解决的题硬是打了Tarjan并且因此鸽了3个月
重构是个好东西
code
#include <bits/stdc++.h>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
#define mod 998244353
#define ll long long
//#define file
using namespace std;
bool a[3001][3001];
bitset<3001> b[3001];
int D[3001],d[3001],n,i,j,k,l,h,t;
ll p[3001],ans1,ans2;
char ch;
int main()
{
#ifdef file
freopen("uoj76.in","r",stdin);
#endif
scanf("%d",&n);
p[0]=1;
fo(i,1,n)
{
p[i]=p[i-1]*2%mod;
fo(j,1,n)
{
ch=getchar();
while (ch!='0' && ch!='1')
ch=getchar();
if (i!=j)
a[i][j]=ch=='0';
else
a[i][j]=0;
D[i]+=a[i][j];
}
}
// ---
fo(i,1,n)
if (!D[i])
d[++t]=i;
while (h<t)
{
++h;
fo(i,1,n)
if (a[i][d[h]])
{
--D[i];
if (!D[i])
d[++t]=i;
}
}
// ---
fo(i,1,n) b[i][i]=1;
fd(i,t,1)
{
fo(j,1,n)
if (a[d[i]][j])
b[j]|=b[d[i]];
}
// ---
fo(i,1,n)
if (!D[i])
{
j=b[i].count();
ans1=(ans1+(p[t]-p[t-j]))%mod;
ans2=(ans2+p[t-j])%mod;
}
printf("%lld %lld\n",(ans1+mod)%mod,(ans2+mod)%mod);
fclose(stdin);
fclose(stdout);
return 0;
}