其他分享
首页 > 其他分享> > 2022年天梯赛-全国总决赛 L3-2 关于深度优先搜索和逆序对的题应该不会很难吧这件事 (30 分)

2022年天梯赛-全国总决赛 L3-2 关于深度优先搜索和逆序对的题应该不会很难吧这件事 (30 分)

作者:互联网

2022天梯赛L3-2

image

前情提要

一个\([1,2,3,4,5,...,n]\)的全排列逆序对和顺序对数目相同
例如:\([1,2,3]\)
他的全排列方案有:

[1,2,3] 逆序对 0 顺序对 3
[1,3,2] 逆序对 1 顺序对 2
[2,1,3] 逆序对 2 顺序对 1
[2,3,1] 逆序对 2 顺序对 1
[3,1,2] 逆序对 1 顺序对 2
[3,2,1]	逆序对 3 顺序对 0

所以一个全排列的逆序对有\(n!/2\)种

分析

1.首先一个树的dfs序的所有排列方式计算方式

image
每一个父亲节点有多少儿子节点的阶乘。
例如样例:\(2!*2!\)

2.如何计算他的祖先们有多少比他大的数

例如:
\(2\) 这个节点,这棵树排列怎么变化,3和5永远在他前面,所以我们可以用树状数组统计出有多少比他的数,再乘上这棵树的排列方式(具体看代码实现)

3.在不同子树中如何计算有多少逆序对

这一步也是这个题的关键
对于一个父节点,他有很多子树,每一次统计这些子树之间有多少逆序对
例如样例:

对于3这个结点
他有两个子树
[4],[5,1,2]
先统计这些区间全排列之间有多少逆序对
根据前情提要全排列逆序对的数目是全排列的一般
所以这一段逆序对的数目2!*(其他父节点的儿子节点的方案是)/2
所以还是这颗树的方案/2,我们已经通过分析1搞出来了
但这样的方案的数目是少的,因为上面分析的每个区间是1,但现在每个区间是他们的size,所以要再乘他们两两之间size的乘积
ans=全排列的方案
所以对于3这个节点,他的子树之间ans/2*siz[4]*siz[5]

代码实现

/*made in mrd*/
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10,mod=1e9+7;
#define lowbit(x) x&-x
#define endl '\n'
#define int long long
#define mem(a,b) memset(a,b,sizeof a)
#define fi first
#define se second
#define lu u<<1
#define ru u<<1|1
#define pb push_back
#define bug1(x) cout<<x<<endl
#define bug2(x,y) cout<<x<<' '<<y<<endl
#define pii pair<int,int>
#define bug3(x,y,z) cout<<x<<' '<<y<<' '<<z<<endl
void init()
{
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
}
vector<int>g[N];
int siz[N];
int num[N];
int res=1;
int fact[N], infact[N];
int sum=0;
int qsm(int a, int b) {
    int res = 1;
    while (b) {
    if (b & 1) res = res * a % mod;
    a = a * a % mod;
    b >>= 1;
    }
    return res;
}
int C(int m, int n) {//组合数
    if (n<0||m<0||m<n) return 0;
    return fact[m] * infact[m - n] % mod * infact[n] % mod;
}
void cinit() {//预处理阶乘
    fact[0] = infact[0] = 1;
    for (int i = 1; i < N; i ++) fact[i] = fact[i - 1] * i % mod;
    infact[N - 1] = qsm(fact[N - 1], mod - 2);
    for (int i = N - 2; i; i --) infact[i] = infact[i + 1] * (i + 1) % mod;
}
void dfs(int u,int fa)//统计size和算出这棵树的方案
{
    int cnt=0;
    for(int i=0;i<g[u].size();i++)
    {
        int j=g[u][i];
        if(j==fa) continue;
        dfs(j,u);
        siz[u]+=siz[j];
        cnt++;
    }
    siz[u]+=1;
    res=res*fact[cnt]%mod;
}    
int tr[N];//树状数组统计他的父亲有多少比他大的
void add(int x,int y) {
    for(int i=x;i<N;i+=lowbit(i)) tr[i]+=y;
}
int ask(int x) {
    int res=0;
    for(int i=x;i;i-=lowbit(i)) res+=tr[i];
    return res;
}
void dfs1(int u,int fa)
{
    sum+=(ask(N-10)-ask(u))*res;//树状数组统计他的父亲有多少比他大的
    sum%=mod;
    add(u,1);
    int pre=0;
    int ans=0;
    for(int i=0;i<g[u].size();i++)
    {
        int j=g[u][i];
        if(j==fa) continue;
        ans=ans+pre*siz[j];
        pre+=siz[j];
        ans%=mod;
    }
    sum+=ans*res%mod*qsm(2,mod-2)%mod;//在不同子树中如何计算有多少逆序对
    for(int i=0;i<g[u].size();i++){
        int j=g[u][i];
        if(j==fa) continue;
        dfs1(j,u);
    }
    add(u,-1);
}
signed main() {
    init();
    cinit();
    int n,root;
    cin>>n>>root;
    for(int i=1;i<n;i++)
    {
        int u,v;
        cin>>u>>v;
        g[u].pb(v);
        g[v].pb(u);
    }
    dfs(root,0);
    //cout<<res<<endl;
    dfs1(root,0);
    cout<<sum<<endl;
    return 0;
}

标签:排列,int,res,30,L3,2022,逆序,节点,define
来源: https://www.cnblogs.com/mrd-T/p/16191361.html