堆/密文/树

二叉堆是一种特殊的堆,其同时满足完全二叉树和堆的性质。完全二叉树:令堆中节点数为?,我们将所有节点编号为\([1,n]\),那么\(1\)号节点为根,其他所有节点满足?号节点的父亲为\(\lfloor\frac{i}{2} \rfloor\)号节点,
其中\(\lfloor\frac{i}{2} \rfloor\)表示\(\frac{i}{2}\)向下取整后的结果。
堆:每个节点的权值均不小于其父亲节点的权值。
求有多少种不同的?个节点的二叉堆满足所有节点权值均在\(1\)\(?\)之间且互不相同,答案对\(10^9 + 7\)取模。

\(n\leq 10^9\)

【样例输入】
3
【样例输出】
2

容易得到\(DP\)方程:
\[ f_n=f_l\cdot f_r \cdot \binom{n-1}{l} \]
仔细观察会发现一个二叉树的两颗子树中至少有一个是满二叉树,所以我们可以预处理满二叉树的答案。

然后组合数部分可以用分块打表的方法预处理阶乘。

代码(没有表的):

#include<bits/stdc++.h>
#define ll long long
#define N 2000005

using namespace std;
inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;}

const ll mod=1e9+7;
ll ksm(ll t,ll x) {
    ll ans=1;
    for(;x;x>>=1,t=t*t%mod)
        if(x&1) ans=ans*t%mod;
    return ans;
}
int n;

ll c[100];
const int blk=5e5;
ll Fac[233]; 
ll Get_fac(int n) {
    int x=n/blk;
    ll ans=Fac[x];
    for(ll i=x*blk+1;i<=n;i++) ans=ans*i%mod;
    return ans;
}
ll Get_C(int n,int m) {return Get_fac(n)*ksm(Get_fac(m),mod-2)%mod*ksm(Get_fac(n-m),mod-2)%mod;}

void Get_biao() {
    c[0]=1;
    c[1]=2;
    c[2]=20;
    c[3]=3432;
    c[4]=155117520;
    c[5]=997262645;
    c[6]=899707189;
    c[7]=876105808;
    c[8]=746311539;
    c[9]=856578165;
    c[10]=8323437;
    c[11]=749503558;
    c[12]=816339384;
    c[13]=429438005;
    c[14]=231982441;
    c[15]=650359664;
    c[16]=626569458;
    c[17]=693597544;
    c[18]=476836356;
    c[19]=807305820;
    c[20]=941911189;
    c[21]=426807449;
    c[22]=900586569;
    c[23]=481824850;
    c[24]=871608524;
    c[25]=88038971;
    c[26]=135219718;
    c[27]=915259147;
    c[28]=930678596;
    c[29]=0;
}
ll size[35];
ll pre[35];
map<int,int>id;
ll dfs(int n) {
    if(n==1||n==0) return 1;
    if(id.find(n)!=id.end()) return pre[id[n]];
    int k=0;
    for(;size[k]*2<=n-1;k++);
    k--;
    int res=n-1-2*size[k];
    if(res>size[k]) {
        res-=size[k]+1;
        return dfs(size[k]<<1|1)*dfs(size[k]+res)%mod*Get_C(n-1,size[k]+res)%mod;
    } else {
        return dfs(size[k]+res)*dfs(size[k])%mod*Get_C(n-1,size[k])%mod;
    }
}

void solve() {
    Get_biao();
    int d=1;
    while((1ll<<d+1)-1<=n) d++;
    size[1]=1;
    for(int i=2;i<=d;i++) {
        size[i]=size[i-1]<<1|1;
    }
    for(int i=1;i<=d;i++) id[size[i]]=i;
    pre[0]=1;
    for(int i=1;i<=d;i++) {
        pre[i]=pre[i-1]*pre[i-1]%mod*Get_C(size[i-1]<<1,size[i-1])%mod;
    }
    cout<<dfs(n)<<"\n";
}

int main() {
    n=Get();
    solve();
    return 0;
}

密文

【问题描述】
有一串长度为\(?\)的密文,密文的每一位都可以用一个非负整数来描述,并且每一位都有一个权值\(?_?\)。你可以进行任意多次操作,每次操作可以选择连续一段密文,花费选择的所有位上权值的异或和的代价获得这段密文每一位的异或和。求至少需要花费多少代价才能将密文的每一位都破解出来。
【输入格式】
第一行一个正整数\(?\)
第二行\(?\)个非负整数,表示\(?_?\)

【样例输入】
2
1 3
【样例输出】
3

\(n\leq 10^5 ,a_i\leq 10^9\)

\(noip\)之前做过的一道题套路完全一样,选择一段区间\([l,r]\)就是得到了前缀异或和\(pre_r\)\(pre_{l-1}\)的关系,我们要得到所有的\(pre_i\)\(pre_0\)的关系。最小生成树。

异或最小生成树就是从高位到低位,将该位上不同的数分为两个集合,集合之间最多只能有一条边。

代码:

#include<bits/stdc++.h>
#define ll long long
#define N 100005

using namespace std;
inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;}

int n;
int a[N];
ll sum[N];
int st[N];
struct trie {
    int sn[2];
}tr[N*35];
int cnt=1;

void Init() {
    for(int i=1;i<=cnt;i++) tr[i].sn[0]=tr[i].sn[1]=0;
    cnt=1;
}
void Insert(int t) {
    int v=1;
    for(int i=29;i>=0;i--) {
        int j=t>>i&1;
        if(!tr[v].sn[j]) tr[v].sn[j]=++cnt;
        v=tr[v].sn[j];
    }
}
int query(int t) {
    int v=1,ans=0;
    for(int i=29;i>=0;i--) {
        int j=t>>i&1;
        if(tr[v].sn[j]) v=tr[v].sn[j];
        else {
            
            ans+=1<<i,v=tr[v].sn[j^1];
        }
    }
    return ans;
}

vector<int>L,R;
ll solve(int l,int r,int dep) {
    if(l>r) return 0;
    if(dep<0) return 0;
    L.clear(),R.clear();
    for(int i=l;i<=r;i++) {
        if(st[i]>>dep&1) R.push_back(st[i]);
        else L.push_back(st[i]);
    }
    int x=L.size();
    for(int i=0;i<x;i++) st[l+i]=L[i];
    for(int i=0;i<R.size();i++) st[l+x+i]=R[i];
    
    ll ans=0;
    if(L.size()&&R.size()) {
        Init();
        for(int i=0;i<L.size();i++) Insert(L[i]);
        int minn=1e9+7;
        for(int i=0;i<R.size();i++) minn=min(minn,query(R[i]));
        ans=minn;
    }
    ans+=solve(l,l+x-1,dep-1)+solve(l+x,r,dep-1);
    return ans;
}

int main() {
    n=Get();
    for(int i=1;i<=n;i++) a[i]=Get();
    for(int i=1;i<=n;i++) sum[i]=sum[i-1]^a[i];
    for(int i=0;i<=n;i++) st[i]=sum[i];
    cout<<solve(0,n,29);
    
    return 0;
}

【问题描述】
给出一棵?个节点的树?以及一棵?个节点的树?,求?有多少个不同的连通子图与?同构,答案对\(10^9 + 7\)取模。我们定义两个图同构当且仅当存在一个节点的对应方案使得每个图中的每个节点恰好与另一个图中的某个节点相对应,且如果在一个图中两个节点之间有连边,它们在另一个图中对应的两个节点之间也有连边。

【样例输入】
4
1 2
1 3
1 4
2
1 2
【样例输出】
3

\(n\leq 2000,m\leq 12\)

我们先给\(B\)树定一个根。

\(f_{i,j}\)表示\(A\)\(i\)节点与\(B\)\(j\)节点为根的树同构的方案数。为了得到\(f_{i,j}\),我们需要将\(i\)的子节点与\(j\)的子节点配对。当然,我们已经的到了\(f_{sn_i,sn_j}\),我们就可以状压\(DP\)得到匹配上\(B\)的所有子节点的方案数。
树hash用括号序比较稳。还要把加法变成异或。

如果\(j\)有几个子节点是同构的,那么会算重\(\prod cnt_i!\)\(cnt_i\)表示第\(i\)中子树的个数。

复杂度\(O(nm^22^m)\)一副很可过的样子。

代码:

#include<bits/stdc++.h>
#define ll long long
#define N 2005
#define M 15

using namespace std;
inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;}

const ll mod=1e9+7;
const ll Mod=998244353;

int n,m;
ll f[N][M];
struct road {int to,next;}s[N<<1];
int h[N],cnt;
void add(int i,int j) {s[++cnt]=(road) {j,h[i]};h[i]=cnt;}
vector<int>e[N];
ll fac[N],ifac[N];
ll ksm(ll t,ll x) {
    ll ans=1;
    for(;x;x>>=1,t=t*t%mod)
        if(x&1) ans=ans*t%mod;
    return ans;
}

ll Hash[N];
ll Hash2[N];
const ll p=666233,p2=134341;
unsigned ll p3=233333;

int size[N];
bool cmp(int a,int b) {
    if(size[a]!=size[b]) return size[a]<size[b];
    if(Hash[a]!=Hash[b]) return Hash[a]<Hash[b];
    return Hash2[a]<Hash2[b];
}

ll t[N];
vector<int>sn[N];
int dep[N],mxdep[N];
void dfs1(int v,int fr) {
    size[v]=1;
    mxdep[v]=dep[v];
    for(int i=0;i<e[v].size();i++) {
        int to=e[v][i];
        if(to==fr) continue ;
        sn[v].push_back(to);
        dep[to]=dep[v]+1;
        dfs1(to,v);
        mxdep[v]=max(mxdep[v],mxdep[to]);
        size[v]+=size[to];
    }
    sort(sn[v].begin(),sn[v].end(),cmp);
    t[v]=1;
    for(int i=0;i<sn[v].size();i++) {
        int j=i;
        while(j+1<sn[v].size()&&size[sn[v][i]]==size[sn[v][j+1]]&&Hash[sn[v][i]]==Hash[sn[v][j+1]]&&Hash2[sn[v][j]]==Hash2[sn[v][j+1]]) j++;
        t[v]=t[v]*ifac[j-i+1]%mod;
        i=j;
    }
    Hash2[v]=Hash[v]='(';
    ll now=1,now2=1;
    for(int i=0;i<sn[v].size();i++) {
        Hash[v]=((Hash[v]*now%mod)^Hash[sn[v][i]])%mod;
        Hash2[v]=((Hash2[v]*now2%Mod)^Hash2[sn[v][i]])%Mod;
        now=now*p%mod;
        now2=now2*p2%Mod;
    }
    Hash[v]=(Hash[v]*p+')')%mod;
    Hash2[v]=(Hash2[v]*p+')')%Mod;
}

int st[N],top;
int cal(int v,int u) {
    if(!top) return 0;
    static ll ans[1<<13],g[1<<13];
    int num=sn[u].size();
    for(int i=0;i<1<<num;i++) ans[i]=0;
    ans[0]=1;
    for(int i=1;i<=top;i++) {
        memcpy(g,ans,sizeof(g));
        int now=st[i];
        for(int j=0;j<num;j++) {
            if(!f[now][sn[u][j]]) continue ;
            int t=1<<j;
            for(int sta=0;sta<1<<num;sta++) {
                if(!(sta&t)) g[sta|t]=(g[sta|t]+ans[sta]*f[now][sn[u][j]])%mod;
            }
        }
        memcpy(ans,g,sizeof(g));
    }
    return ans[(1<<num)-1]*t[u]%mod;
}

ll ans;
int rt;
void solve(int v,int fr) {
    for(int i=1;i<=m;i++) if(sn[i].size()==0) f[v][i]=1;
    for(int i=h[v];i;i=s[i].next) {
        int to=s[i].to;
        if(to==fr) continue ;
        solve(to,v);
    }
    top=0;
    for(int i=h[v];i;i=s[i].next) {
        if(s[i].to==fr) continue ;
        st[++top]=s[i].to;
    }
    for(int i=1;i<=m;i++) {
        if(sn[i].size()) {
            f[v][i]=cal(v,i);
        }
    }
    (ans+=f[v][rt])%=mod;
}

#define pr pair<int,int>
#define mp(a,b) make_pair(a,b)
map<pr,int>vis;
int work() {
    for(int i=1;i<=m;i++) sn[i].clear();
    for(int i=1;i<=m;i++) Hash[i]=Hash2[i]=0;
    dep[rt]=1;
    dfs1(rt,0);
    if(vis.find(mp(Hash[rt],Hash2[rt]))!=vis.end()) return 0;
    vis[mp(Hash[rt],Hash2[rt])]=1;
    memset(f,0,sizeof(f));
    ans=0;
    solve(1,0);
    return ans;
}

int main() {
    n=Get();
    int a,b;
    for(int i=1;i<n;i++) {
        a=Get(),b=Get();
        add(a,b),add(b,a);
    }
    m=Get();
    fac[0]=1;
    for(int i=1;i<=m;i++) fac[i]=fac[i-1]*i%mod;
    ifac[m]=ksm(fac[m],mod-2);
    for(int i=m-1;i>=0;i--) ifac[i]=ifac[i+1]*(i+1)%mod;
    
    for(int i=1;i<m;i++) {
        a=Get(),b=Get();
        e[a].push_back(b);
        e[b].push_back(a);
    }
    ll tot=0;
    for(rt=m;rt>=1;rt--) (tot+=work())%=mod;
    cout<<tot<<"\n";
    
    return 0;
}

转载于:https://www.cnblogs.com/hchhch233/p/10495852.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值