UOJ #76 【UR #6】懒癌
确实是一道很不错的题啊。
题意
感觉也没什么特别简洁的版本,大家直接看题面吧。
题解
我第一次看到这个类似问题的背景是疯狗,因此下面的题解不自觉的代入了...大家明白意思就好。
我们考虑对于疯狗,我们将其染为黑点,否则是白点。这样我们就可以用一张每个点有两种颜色的有向图来表示当前的状态。来想一想状态之间是如何转移的。
首先我们可以对每一个疯狗的主人分开考虑,计算他什么时候会发现自己的狗是疯狗,对所有的答案取
那么首先要考虑的问题是怎么判断一个状态是否会永远发现不了疯狗。那么比较显然应该是转移出现了环的时候。那么什么样的图的转移会成环呢?我们假设图中有一个黑点,它可以到达图中的一个环,那么我们在转移的时候显然有一种转移是不断的把接近这个环的后继染黑,直到走到环上,接下来沿着环染黑,那么转移就会无休无止了。更准确的说,当存在一个黑点能够到达一个点数大于
于是我们可以发现所有能到达点数大于
于是我们只需要考虑一张每个点有两种颜色的
证明我们采用归纳法:假设对于一个状态,它能转移到的所有状态都符合这个结论了。那么我们依次考虑每个黑点,它可以将它的后继中的任意个染黑然后自己变白(当然要满足图中至少一个黑点),这样之后转移得到的答案是新的点集能到达的点数。那么由于它想要的是最大值,不难发现将所有后继染黑一定是最优的。此时除了它本身以外,它所能到达的点依然可以到达。也就是说,假如当前的其它黑点中有能够到达它的点,那么这个点的答案就是当前点集能到达的点数
而我们需要的是所有点的答案的最小值,不难发现点集中一定有一个黑点是其它黑点无法到达的(比如拓扑序最大的点),因此“当前点集能到达的点数”这个下界总是可以取到。于是结论就成立了。
那么考虑怎么求第一问的答案。可以每个点分开算贡献。我们先用
至于第二问,不难发现是求每个状态有多少点可以取到最小值。根据之前的分析,应该是不能被点集中除自己外的点到达的那些点。分开算到每个点中,就是仅包含自己和不能到自己的点的那些点集。
代码:
#include<cstdio>
#include<bitset>
#include<stack>
using std::bitset;
using std::stack;
const int mod=998244353;
inline int add(int a,int b)
{
return (a+=b)>=mod?a-mod:a;
}
inline int sub(int a,int b)
{
return (a-=b)<0?a+mod:a;
}
inline int mul(int a,int b)
{
return (long long)a*b%mod;
}
inline int qpow(int a,int b)
{
int res=1;
for(;b;a=mul(a,a),b>>=1)
if(b&1)
res=mul(res,a);
return res;
}
const int N=3005;
int n,ans,idx,cnt;
char s[N];
int g[N][N];
int dfn[N],low[N],com[N];
int size[N];
int g2[N][N];
stack<int> st;
bool inst[N];
bitset<N> from[N];
void dfs(int x)
{
register int i;
dfn[x]=low[x]=++idx;
st.push(x);inst[x]=1;
for(i=1;i<=n;i++)
if(g[x][i])
{
if(!dfn[i])
{
dfs(i);
if(low[i]<low[x])
low[x]=low[i];
}
else if(inst[i]&&dfn[i]<low[x])
low[x]=i;
}
if(dfn[x]==low[x])
{
com[x]=++cnt;size[cnt]=1;
while(st.top()!=x)
com[st.top()]=cnt,size[cnt]++,inst[st.top()]=0,st.pop();
inst[x]=0;st.pop();
}
return;
}
signed main()
{
register int i,j;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%s",s+1);
for(j=1;j<=n;j++)
if(i!=j&&s[j]=='0')
g[i][j]=1;
}
for(i=1;i<=n;i++)
if(!dfn[i])
dfs(i);
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(g[i][j]&&com[i]!=com[j])
g2[com[i]][com[j]]=1;
for(i=1;i<=cnt;i++)
for(j=1;j<i;j++)
if(g2[i][j]&&size[j]>size[i])
size[i]=size[j];
int cc=0;
for(i=1;i<=cnt;i++)
if(size[i]==1)
cc++;
// for(i=1;i<=cnt;i++)
// if(size[i]==1)
// {
// to[i].set(i);
// for(j=1;j<i;j++)
// if(size[j]==1&&g2[i][j])
// to[i]|=to[j];
// }
for(i=cnt;i>=1;i--)
if(size[i]==1)
{
from[i].set(i);
for(j=cnt;j>i;j--)
if(size[j]==1&&g2[j][i])
from[i]|=from[j];
}
for(i=1;i<=cnt;i++)
if(size[i]==1)
ans=add(ans,mul(sub(qpow(2,from[i].count()),1),qpow(2,cc-from[i].count())));
printf("%d ",ans);
ans=0;
for(i=1;i<=cnt;i++)
if(size[i]==1)
ans=add(ans,qpow(2,cc-from[i].count()));
printf("%d\n",ans);
return 0;
}