题解 P5575 【[CmdOI2019]黑白图】
command_block · · 题解
官方题解:黑白图
首先大概看得出来这是个(链/树/基环树)上
(感觉自己想复杂了,为什么泥萌的解法都这么神奇)
1~#2
枚举黑白状态,算出相应概率,再bfs计算联通块大小k次方和即可。
熟悉期望的定义就可以写出来。
这都写不来劝你别碰这道题……
#include<cstdio>
#include<vector>
#define mod 998244353
using namespace std;
int k;
vector<int> g[55];
long long p[55];
long long powM(long long a,long long t=mod-2)
{
long long ans=1,buf=a;
while(t){
if(t&1)ans=(ans*buf)%mod;
buf=(buf*buf)%mod;
t>>=1;
}return ans;
}
void addline(int from,int to)
{
g[from].push_back(to);
g[to].push_back(from);
}
long long pk[55];
bool b[55],e[55];
int dfs(int num)
{
e[num]=1;
int tot=1;
for (int i=0,v;i<g[num].size();i++)
if (!e[g[num][i]]&&b[g[num][i]])
tot+=dfs(g[num][i]);
return tot;
}
long long ans;
int n,m;
void dfspre(int pos,long long val)
{
if (pos>n){
for (int i=1;i<=n;i++)e[i]=0;
long long sum=0;
for (int i=1;i<=n;i++)
if(!e[i]&&b[i])sum+=pk[dfs(i)];
ans=(ans+sum*val)%mod;
return ;
}b[pos]=0;dfspre(pos+1,val*(1-p[pos]+mod)%mod);
b[pos]=1;dfspre(pos+1,val*p[pos]%mod);
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
for (int i=1;i<=n;i++){
scanf("%lld",&p[i]);
p[i]=p[i]*powM(100)%mod;
}for (int i=1;i<=n;i++)pk[i]=powM(i,k);
for (int i=1,from,to;i<=m;i++){
scanf("%d%d",&from,&to);
addline(from,to);
}dfspre(1,1);
printf("%lld",ans);
return 0;
}
50/50特殊性质
留给那些只会数方案数的数数选手(我想这不可能有吧)
一条链
见P1654 OSU!
所以这道题就是把OSU上了树/基环树。
简单讲讲高次期望的套路:
我们从链的左端开始:
设
然后根据
即
这就可以转移了。
假设这个节点是白色,那么
别忘了把贡献乘以对应概率。
最后统计得到:
(这类似于卷积)
总的复杂度
答案就是
代码就不给了
树
几乎一样的套路。
设
值得注意的是树的点里面有一个
#include<string>
#include<cstdio>
#include<vector>
#define MaxN 200500
#define mod 998244353
#define ll long long
using namespace std;
inline int read()
{
int X=0;char ch=0;
while(ch<48||ch>57)ch=getchar();
while(ch>=48&&ch<=57)X=X*10+(ch^48),ch=getchar();
return X;
}
int n,k;
ll p[MaxN],f[MaxN][6],sum[MaxN];
vector<int> g[MaxN];
void addline(int from,int to)
{
g[from].push_back(to);
g[to].push_back(from);
}
ll C[6][6];
void dfs(int num)
{
f[num][0]=1;
for (int i=0,v;i<g[num].size();i++)
if (!f[v=g[num][i]][0]){
dfs(v);
sum[num]=(sum[num]+(mod+1-p[num])*f[v][k]+sum[v])%mod;
for (int j=k;j;j--){
ll sav=0;
for (int p=0;p<=j;p++)
sav=(sav+C[j][p]*f[num][p]%mod*f[v][j-p])%mod;
f[num][j]=sav;
}
}
for (int j=k;j;j--){
ll sav=0;
for (int p=0;p<=j;p++)
sav=(sav+C[j][p]*f[num][p])%mod;
f[num][j]=sav*p[num]%mod;
}
}
int m;
void solve()
{
for (int i=1;i<=n;i++)
p[i]=828542813ll*read()%mod;
for (int i=1,from;i<n;i++){
from=read();
addline(from,read());
}
for (int i=0;i<=k;i++)
for (int j=0;j<=i;j++){
C[i][j]=1;
for (int p=1;p<=i;p++)C[i][j]*=p;
for (int p=1;p<=j;p++)C[i][j]/=p;
for (int p=1;p<=i-j;p++)C[i][j]/=p;
}
dfs(1);
printf("%lld",(f[1][k]+sum[1])%mod);
}
int main()
{
n=read();m=read();k=read();
if (n-1==m)solve();
return 0;
}
基环树
开始毒瘤了。
首先把环上挂的每一棵树单独做一次
我们发现前面的模型
设
最后的式子就是:
好了现在这变成了一个环上的问题。
假如这个环上有一个点必为白色的话,就相当于把环断成了链。
我们考虑容斥,先假设第一个点为白色。
我们真的把这个点变为白色,并且在子树里局部
其他的节点就变为序列问题了,链上dp
那么第一个点为白色的贡献就计算完毕,我们再把第一个点变为为黑色,并且再次局部
由于只会对换上的每个节点局部
此时不能直接变为序列问题,我们再对下一个点讨论。
……
最后,所有点都变成了黑色,则可以随便断掉一条边,反正贡献易算。
注意所有贡献都要乘以对应概率!!!
暴力跑序列
如何优化?注意到我们每次只是强制将一个点变黑或者变白,却要重新链上
于是乎我们可以分析一下性质。
把某个点变成0,就是把这个环断成了两截。
11111 0 u...v
因为这是个环,我们要做的就是求出:
u...v11111的期望贡献(注意拼接顺序)。
u...v是一段后缀,可以对于后缀的
11111的可以直接算了。
那么简单的把这两个
布星!
由于从后往前计算,u...v的更新点在左边,我们算出的是
u...v11111的话两个端口接不上,没法合并……也就是说我们要计算的是
观察到
设 u...v左边跟了....?,即....?u...v
对于....?我们把
由于线性的贡献,....?u...v最后的
其中u...v具体控制的。
所以我们在不知道....?的情况下,计算出
然后任意给定一些东西接在u...v前面,我们都可以快速算出
也就解决了端点的问题。
如何
我们设
原来式子里的加法变成列向量加法就好,数乘变成向量数乘。
有了这个东西我们可以不采用往后加的方式,还可以采用往前加的方式,突破了端点的限制。
我们就可以用
Code:
奇长无比……
之前的std爆long long了,导致两个数据出锅。
这里对赛时AC的巨佬@shdut表示歉意,同时也感谢他找到了辣鸡std的锅。
#include<cstdio>
#include<vector>
#define MaxN 200500
#define mod 998244353
#define ll long long
using namespace std;
inline int read()
{
int X=0;char ch=0;
while(ch<48||ch>57)ch=getchar();
while(ch>=48&&ch<=57)X=X*10+(ch^48),ch=getchar();
return X;
}
int n,k;
ll p[MaxN],f[MaxN][6],sum[MaxN];
vector<int> g[MaxN];
bool e[MaxN];
void addline(int from,int to)
{
g[from].push_back(to);
g[to].push_back(from);
}
ll C[6][6];int flagp;
//处理单颗树
void dfs1(int num)
{
f[num][0]=1;
for (int i=0,v;i<g[num].size();i++)
if (!f[v=g[num][i]][0]){
dfs1(v);
sum[num]=(sum[num]+(mod+1-p[num])*f[v][k]+sum[v])%mod;
for (int j=k;j;j--){
ll sav=0;
for (int p=0;p<=j;p++)
sav=(sav+C[j][p]*f[num][p]%mod*f[v][j-p])%mod;
f[num][j]=sav;
}
}
if (num!=flagp)
for (int j=k;j;j--){
ll sav=0;
for (int p=0;p<=j;p++)
sav=(sav+C[j][p]*f[num][p])%mod;
f[num][j]=sav*p[num]%mod;
}
}
int vis[MaxN],tp,tot,rng[MaxN];
//找环
void dfs2(int num,int fa)
{
vis[num]=1;
for (int i=0,v;i<g[num].size();i++)
if ((v=g[num][i])!=fa){
if (vis[v]){tp=v;break ;}
dfs2(v,num);
if (tp||tot)break ;
}
if (tp)rng[++tot]=num;
if (num==tp)tp=0;
}
struct Vec
{
//向量结构体
ll x[6];
inline void clear()
{for (int i=0;i<=k;i++)x[i]=0;}
Vec operator + (const Vec &B) const{
Vec ret;
for (int i=0;i<=k;i++)
ret.x[i]=(B.x[i]+x[i])%mod;
return ret;
}
Vec operator * (const ll &B) const{
Vec ret;
for (int i=0;i<=k;i++)
ret.x[i]=x[i]*B%mod;
return ret;
}
}ff[MaxN][6],s[MaxN];
ll h[MaxN][6],hp[MaxN];
void solve2()
{
//找环
dfs2(1,0);
rng[0]=rng[tot];
rng[tot+1]=rng[1];
ll ans=0;
//得出挂在环上的每棵树的信息
for (int i=1;i<=tot;i++){
f[flagp=rng[i]][0]=0;
f[rng[i-1]][0]=f[rng[i+1]][0]=1;
dfs1(rng[i]);
ans=(ans+sum[rng[i]])%mod;
hp[i]=p[rng[i]];
for (int j=0;j<=k;j++)
h[i][j]=f[rng[i]][j];
}
//正序预处理
for (int i=0;i<=k;i++)ff[0][i].x[i]=1;
for (int i=1;i<=tot;i++){
ff[i][0].x[0]=1;
s[i]=s[i]+ff[i-1][k]*(mod+1-hp[i])+s[i-1];
for (int j=k;j;j--){
Vec sav; sav.clear();
for (int p=0;p<=j;p++)
sav=sav+ff[i-1][j-p]*C[j][p]*h[i][p];
ff[i][j]=sav;
}
for (int j=k;j;j--){
Vec sav; sav.clear();
for (int p=0;p<=j;p++)
sav=sav+ff[i][p]*C[j][p];
ff[i][j]=sav*hp[i];
}
}
for (int i=0;i<=tot;i++)
for (int j=0;j<=k;j++)
s[i].x[j]=(s[i].x[j]+ff[i][k].x[j])%mod;
//倒序容斥,buf是对应概率,pp是将要拼接的信息
ll pp[6],buf=1; pp[0]=1;
for (int i=1;i<=k;i++)pp[i]=0;
for (int i=tot;i;i--){
ll savbuf=buf;
buf=buf*(mod+1-hp[i])%mod;
ll sav=0;
for (int j=0;j<=k;j++)
sav=(sav+pp[j]*s[i-1].x[j])%mod;
ans=(ans+buf*sav)%mod;
//贡献
buf=savbuf*hp[i]%mod;
for (int j=k;j;j--){
ll sav=0;
for (int p=0;p<=j;p++)
sav=(sav+C[j][p]*pp[j-p]%mod*h[i][p])%mod;
pp[j]=sav;
}
for (int j=k;j;j--){
ll sav=0;
for (int p=0;p<=j;p++)
sav=(sav+C[j][p]*pp[p])%mod;
pp[j]=sav;
}
}//最后变成一整个环的情况
ans=(ans+pp[k]*buf)%mod;
printf("%lld\n",ans);
}
int m;
int main()
{
n=read();m=read();k=read();
for (int i=1;i<=n;i++)
p[i]=828542813ll*read()%mod;
for (int i=1,from;i<=m;i++){
from=read();
addline(from,read());
}
for (int i=0;i<=k;i++)
for (int j=0;j<=i;j++){
C[i][j]=1;
for (int p=1;p<=i;p++)C[i][j]*=p;
for (int p=1;p<=j;p++)C[i][j]/=p;
for (int p=1;p<=i-j;p++)C[i][j]/=p;
}
if (m==n-1){
dfs1(1);
printf("%lld",(f[1][k]+sum[1])%mod);
}else solve2();
return 0;
}