18.11.5绍一模拟赛
T1树
题意
给定一棵\(n(n\le 10^7)\)个点,一开始点全是黑的树,每次随机选一个黑点,把从他到根节点的路径上的点全部变成白色。
求把树全部染白的期望染色次数模\(998244353\)意义下的结果。
分析
我们想要把一棵树染成白色,发现如果我们染了根节点,次数就会加一。
如果没有染根节点,根节点会自动被染好。
那么染好一棵树的期望次数就是把它左右子树染好的期望次数+染了根节点从期望次数。
\(f[i]=\sum_k^{k\in child[i]}{f[k]+}\frac{1}{siz[i]}\)
然后打了个记搜爆了。。。。
然后打了个从叶子递推爆了。。。。
然而并不理解为什么子节点的序号一定比父节点大。
直接从\(n\)循环到\(1\)就过了。。
代码
记搜
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#define ll long long
#define file "tree"
using namespace std;
const int N=10000009;
const int mod=998244353;
struct Node{
int siz;
vector<int>son;
}tree[N];
int read(){
char c;int num,f=1;
while(c=getchar(),!isdigit(c))if(c=='-')f=-1;num=c-'0';
while(c=getchar(), isdigit(c))num=num*10+c-'0';
return f*num;
}
int Pow(int a,int p){
int ans=1;
for(;p;p>>=1,a=(1ll*a*a)%mod)
if(p&1)ans=1ll*a*ans%mod;
//cout<<ans<<endl;
return ans;
}
int inv(int x){
return Pow(x,mod-2);
}
void dfs(int x){
for(int i=0;i<tree[x].son.size();i++){
dfs(tree[x].son[i]);
tree[x].siz+=tree[tree[x].son[i]].siz;
}
tree[x].siz+=1;
}
int n,m,f[N];
int get(int x){
if(f[x])return f[x];
int tot=tree[x].siz,k=inv(tot),tmp=0;
for(int i=0;i<tree[x].son.size();i++){
tmp+=get(tree[x].son[i]);
if(tmp>=mod)tmp-=mod;
}
f[x]=tmp+k;
if(f[x]>=mod)f[x]-=mod;
return f[x];
}
int main()
{
freopen(file".in","r",stdin);
freopen(file".out","w",stdout);
n=read();
for(int i=1;i<n;i++)
tree[read()].son.push_back(i+1);
dfs(1);
printf("%d\n",get(1));
return 0;
}
从叶子递推
#include <bits/stdc++.h>
using namespace std;
const int N=10000009;
const int mod=998244353;
int read(){
char c;int num,f=1;
while(c=getchar(),!isdigit(c))if(c=='-')f=-1;num=c-'0';
while(c=getchar(), isdigit(c))num=num*10+c-'0';
return f*num;
}
int n,fa[N],ch[N],f[N];
int siz[N],inv[N];
bool leaf[N];
void add(int &a,int b){
a+=b;
if(a>=mod)a-=mod;
}
int Pow(int a,int p){
int ans=1;
for(;p;p>>=1,a=1ll*a*a%mod)
if(p&1)ans=1ll*ans*a%mod;
return ans;
}
void init(){
inv[1]=1;
for(int i=2;i<=10000002;i++)
inv[i]=(1ll*mod-mod/i)*inv[mod%i]%mod;
}
void push_up(int x){
int k;
while(x&&ch[x]==0){
add(siz[x],1);
add(siz[fa[x]],siz[x]);
//累加到父节点
ch[fa[x]]--;
//父节点儿子统计
k=inv[siz[x]];
add(f[x],k);
//计算当前期望
add(f[fa[x]],f[x]);
//累加父亲期望
x=fa[x];
//继续查找父亲
}
}
int main()
{
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
n=read();init();
for(int i=2;i<=n;i++){
fa[i]=read();
ch[fa[i]]++;
leaf[fa[i]]=1;
}
for(int i=1;i<=n;i++){
if(!leaf[i])push_up(i);
}
printf("%d\n",f[1]);
return 0;
}
循环
#include <bits/stdc++.h>
using namespace std;
const int N=10000009;
const int mod=998244353;
int read(){
char c;int num,f=1;
while(c=getchar(),!isdigit(c))if(c=='-')f=-1;num=c-'0';
while(c=getchar(), isdigit(c))num=num*10+c-'0';
return f*num;
}
int n,fa[N],ch[N],f[N];
int siz[N],inv[N];
bool leaf[N];
void add(int &a,int b){
a+=b;
if(a>=mod)a-=mod;
}
int Pow(int a,int p){
int ans=1;
for(;p;p>>=1,a=1ll*a*a%mod)
if(p&1)ans=1ll*ans*a%mod;
return ans;
}
void init(){
inv[1]=1;
for(int i=2;i<=10000002;i++)
inv[i]=(1ll*mod-mod/i)*inv[mod%i]%mod;
}
int main()
{
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
n=read();init();
for(int i=2;i<=n;i++)fa[i]=read();
for(int i=n;i>=1;i--){
siz[i]++;
siz[fa[i]]+=siz[i];
add(f[i],inv[siz[i]]);
add(f[fa[i]],f[i]);
}
printf("%d\n",f[1]);
return 0;
}
T2图
题意
求一张图\(n\le 18\)的\(dfs\)序数量
分析
发现\(n\)很小。应该是个爆搜或者状压。
我的写法是记搜+状压。
可以从任意一点开始深搜,那么我们可以新建一个虚拟节点\(n+1\)号点,然后钦定\(n+1\)为第一个遍历的点,然后进行记搜。
\(g[s][i]\)表示已遍历的节点状态\(s\),现在从\(i\)点开始跑,能抵达的节点的状态(包括\(i\)但不包括以前的节点)。
\(f[s][i]\)表示已经遍历\(s\)状态,现在从\(i\)节点开始遍历,以\(i\)为根的搜索树的数量。
从\(i\)号点开始寻找出点,我们考虑出点之间的关系。
如果两个出点在删除掉已经遍历的点之后仍然联通,那么这两个节点肯定互相影响,也就是说对一个点进行深搜,回溯的时候不能搜另外一个点。
所以说这两个点的dfs序数量满足加法原理。
如果两个点在删除掉已经遍历的点之后不连通,那么这两个节点没有任何关系,换句话说,不论一个点的遍历顺序怎么样,另外一个点的遍历顺序都可以随便取。
那么这两个点满足乘法原理。
我们对一个残图的一个点进行深搜,搜索出每个出边能遍历到的点,搜索出若干个点集,每个点集相互独立,点集内的\(dfs\)序也是独立的。
我们只要把点集的\(dfs\)序全部相乘,然后再乘上这些点集的排列数,就是当前状态了。
代码
删掉注释70行不到,感觉还是不长的。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#define ll long long
#define file "graph"
using namespace std;
const int mod=998244353;
const int N=20,M=N*(N-1)*2;
int read(){
char c;int num,f=1;
while(c=getchar(),!isdigit(c))if(c=='-')f=-1;num=c-'0';
while(c=getchar(), isdigit(c))num=num*10+c-'0';
return f*num;
}
int n,m,vis[N],ans=0,f[(1<<N)+1][N];
int head[N],nxt[M],ver[M],tot=1,g[(1<<N)+1][N];
void add(int u,int v){
ver[++tot]=v;nxt[tot]=head[u];head[u]=tot;
ver[++tot]=u;nxt[tot]=head[v];head[v]=tot;
}
void print(int x){
while(x){
cout<<(x&1);
x>>=1;
}
}
int dfs1(int s,int x){
if(g[s][x])return g[s][x];
g[s][x]|=(1<<x-1);
//if(x==2)cout<<s<<endl;
for(int i=head[x];i;i=nxt[i]){
int y=ver[i];
//if(x==2&&y==1)cout<<(s&(1<<y-1))<<endl;
if(s&(1<<y-1))continue;
dfs1(s|((1<<y-1)),y);
g[s][x]|=g[s|((1<<y-1))][y];
}
return g[s][x];
}
//以当前点为根节点的搜索树的状态
int dfs(int s,int u){
if(f[s][u])return f[s][u];
f[s][u]=1;
if(!head[u])return f[s][u];
int gg[20][4],cnt=0,k,flag=0;
memset(gg,0,sizeof(gg));
//print(s);cout<<" "<<u<<endl;
for(int i=head[u];i;i=nxt[i]){
int y=ver[i];flag=0;
if(s&(1<<y-1))continue;
k=dfs1(s|(1<<y-1),y);
//k是状态
//if(u==4&&s==8&&k==6)cout<<y<<endl;
for(int j=1;j<=cnt;j++){
if(gg[j][2]==k){
flag=1;gg[j][1]+=dfs(s|(1<<y-1),y);
if(gg[j][1]>=mod)gg[j][1]-=mod;
break;
}
}
//遍历原来的状态
if(!flag){
gg[++cnt][1]=dfs(s|(1<<y-1),y);
gg[cnt][2]=k;
}
//新开状态
}
/*if(u==4&&s==8){
cout<<gg[1][2]<<endl;
}*/
for(int i=1;i<=cnt;i++)
f[s][u]=1ll*f[s][u]*i%mod*gg[i][1]%mod;
return f[s][u];
}
int main()
{
freopen(file".in","r",stdin);
freopen(file".out","w",stdout);
n=read();m=read();
for(int i=1;i<=m;i++){
int u=read(),v=read();
add(u,v);
}
for(int i=1;i<=n;i++){
add(n+1,i);
}
dfs((1<<n),n+1);
//cout<<g[12][3]<<endl;
printf("%d\n",f[1<<n][n+1]);
return 0;
}
T3集合
题意
给定\(n,m(n,m\le 2000)\),构造两个没有交集的集合,要求\(A\)集合里的数小于\(n\),\(B\)集合里的数小于\(m\)。
并且\(A\)集合的异或和小于\(B\)集合。
求构造的方案数。
分析
不会写啊啊啊啊啊啊!!!!
(逃了)