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\)集合。
求构造的方案数。

分析

不会写啊啊啊啊啊啊!!!!
(逃了)

转载于:https://www.cnblogs.com/onglublog/p/9909288.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值