计数杂题

P4071 [SDOI2016] 排列计数 :https://www.luogu.com.cn/problem/P4071

思路:题目要求序列中m个数下标等于自身,其余n-m个数满足错排。那么每次在n个位置中选出m个 a[i]=i 的位置,之后我们再用错排公式求出n-m的错排,最后用乘法原理即可。

int f[maxn],g[maxn],d[maxn];
void init()
{
    f[0]=g[0]=1;
    d[1]=0,d[2]=1;
    for(int i=1;i<=maxn;i++)
    {
        f[i]=f[i-1]*i%mod;
        g[i]=g[i-1]*qpow(i,mod-2)%mod;
    }
    for(int i=3;i<=maxn;i++)
    {
        d[i]=(i-1)*(d[i-1]+d[i-2])%mod;//错排公式
    }
}
int C(int n,int m)
{
    return f[n]*g[m]%mod*g[n-m]%mod;
}//组合数
void solve()
{
    int n,m;
    cin>>n>>m;
    if(n==m)
    {
        cout<<1<<'\n';
        return ;
    }
    else 
    {
        cout<<C(n,m)*d[n-m]%mod<<'\n';
    }
}

P3182 [HAOI2016] 放棋子 :https://www.luogu.com.cn/problem/P3182

思路:我们可以发现题目给的要求是“每行、每列都有且仅有一个障碍”,要求放的棋子也是相同的要求。那么我们就可以把二维坐标系转化为一维。
我们准备放N个棋子,既然每行、每列都有且仅有一个障碍,那么我们可以知道转化成一维坐标系之后,对于每个即将被放置的棋子,肯定是有且仅有一个位置是不能放的。那么我们想到错排问题也是有且仅有一个位置是不能放的,就可以把这道题直接转化为错排即可。这里数据范围较大,可以直接用py。

import sys
input = sys.stdin.readline
f = [0,0,1]
n = int(input())
for i in range(3,n+1):
    f.append((i-1)*(f[i-1]+f[i-2]))
print((f[n]))

P2606 [ZJOI2010] 排列计数:https://www.luogu.com.cn/problem/P2606
思路:
首先我们会议一下关于二叉树的知识点:一个节点 u 的两个子节点的编号一定是2u和2u+1。
再回来看这道题:题目要求 p[i] > p[floor(i/2)] ,那么转变一下式子可得 p[i2] > p[i] && p[i2+1] > p[i] ,这个条件也就是说明一个节点的两个子节点比父节点大,也就是满足小根堆的性质。那么这道题我们可以先预处理出每个节点有多少子节点,最后根据小根堆的性质统计答案即可。设 dp[i] 为当前有多少个大小为i的小根堆,从深度大往深度小转移。假设当前遍历到节点 u ,该节点的左右子树共有C(s[u]-1,s[2u]) 种情况(即选择s[2u]个节点作为左子树,剩下的都为右子树),u 的两个子节点都已经在之前处理过了,直接乘法原理统计答案即可。
这题要求答案mod一个质数p,要用到Lucas定理。

#define int long long
int n,p;
#define maxn 4000011
ll qpow(ll a,ll b) {ll res=1;a%=p; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%p;a=a*a%p;}return res;}
int f[maxn];
int dp[maxn],s[maxn];
int C(int n,int m)
{
    return f[n]*qpow(f[n-m]*f[m]%p,p-2)%p;
}
int lucas(int n,int m)
{
    if(m==0) return 1;
    return lucas(n/p,m/p)*C(n%p,m%p)%p;
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>p;
    f[0]=1;
    for(int i=1;i<=n;i++)
    {
        f[i]=f[i-1]*i%p;
    }
    for(int i=1;i<=n;i++)
    {
        s[i]=1;
    }
    for(int i=n;i>=2;i--)
    {
        s[i>>1]+=s[i];
    }
    for(int i=n+1;i<=n*2+1;i++)
    {
        dp[i]=1;
    }
    for(int i=n;i>=1;i--)
    {
        dp[i]=lucas(s[i]-1,s[i*2])%p*dp[i*2]%p*dp[i*2+1]%p;
    }
    cout<<dp[1]<<'\n';
}
posted @ 2024-04-17 20:07  Captainfly19  阅读(5)  评论(0编辑  收藏  举报