首页 > 其他分享 >【20zr提高组十连测day10】心

【20zr提高组十连测day10】心

时间:2024-09-27 13:48:44浏览次数:10  
标签:组十连测 siz ll 20zr day10 操作 frac prod dp

【20zr提高组十连测day10】心

首先不同的操作序列一定在某个时刻使数组内容不同,因此我们只需要统计合法的操作序列数量。

一个合法的最终数组形如若干个 \(1,M\),而且 \(1,M\) 之间可能有若干个 \(x\),长度为 \(n+1\)。

造成这个数组的操作序列必须满足所有操作 \(1,M\) 按顺序排列,把操作 \(1,M\) 称作关键操作,所有操作 \(x\) 出现在它后面第一个关键操作的后面。

\(u\) 必须出现在 \(v\) 后面,我们连一条 \(u\to v\) 的边,也就是说我们要先遍历 \(v\) 再遍历 \(u\),拓扑序计数即可,而且是树上拓扑序计数。

树上拓扑序(先选叶子再选儿子的拓扑序)计数可以树形 DP 解决。设 \(f_u\) 表示 \(u\) 在子树的拓扑序数量,显然 \(u\) 的儿子子树间是互不干扰的,但是子树内的顺序是一定的,\(u\) 一定是放在最后,有转移方程:

\[f_u=(siz_u-1)!\prod_{v\in son_u} \frac{f_v}{siz_v!} \]

也可以这么算:

\[f_u=siz_u! (\prod_{v\in tree_u} \frac{1}{siz_i} ) \]

用数学归纳法证明:

\(n=1\) 时显然成立。

假设 \(n<siz_u\) 时成立。

\[f_u=(siz_u-1)!(\prod_{v \in son_u}\frac{siz_v ! \prod_{i\in tree_v} \frac{1}{siz_v}}{siz_v!})\\ =(siz_u-1)!(\prod_{v\in tree_u,v\not= u} \frac{1}{siz_v})\\ =siz_u! (\prod_{v\in tree_u} \frac{1}{siz_i} ) \]

这个也可以理解成每个结点一定在整个子树序列的最后一位,所以答案是所有节点的全排列去掉每个结点在子树其他位置的情况。

回到题目,我们可以做一个 \(n^2\) 的 DP。

\(dp_i\) 表示前 \(i\) 个数字,有若干个空的排列,形成这个排列的操作方案数。

\[\texttt{加上一个和前一位相同的关键数字:}dp_i\cdot \frac{1}{n-i}\to dp_{i+1}\\ \texttt{加上一个和前一位不同的关键数字:}dp_i\cdot \binom{m-2}{k}\cdot \frac{1}{i+k+1}\to dp_{n-i}(0\le k \le n-i-1) \]

答案就是 \(dp_n\cdot n!\)。

Code

#include<bits/stdc++.h>
// #define DEBUG
#define sf scanf
#define pf printf
#define rep(x,y,z) for(int x=(y);x<=(z);x++)
using namespace std;
typedef long long ll;
const int N=3e3+4,M=1e8+7,mod=998244353;
int n,m;
ll dp[N];
void add(ll &a,ll b){a= (a+b>mod?a+b-mod:a+b);}
ll ans;
ll inv[N],mul[N];
ll ksm(ll a,ll b){
    ll s=1;
    while(b){
        if(b&1) s=s*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return s;
}
ll ifac[N];
ll del(ll a,ll b){return (a-b<0?a-b+mod:a-b);}
void init(){
    inv[1]=1;ifac[0]=ifac[1]=1;
    rep(i,2,n) inv[i]=ksm(i,mod-2),ifac[i]=ifac[i-1]*inv[i]%mod;
    mul[0]=1;
    rep(i,1,n){
        if(m-2-i+1<=0){
            break;
        }
        mul[i]=mul[i-1]*(m-2-i+1)%mod;
    }
}
int main(){
    #ifdef DEBUG
    freopen("ex_B1.in","r",stdin);
    freopen("my.out","w",stdout);
    #endif
    sf("%d%d",&n,&m);
    init();
    dp[0]=1;
    rep(i,0,n-1){
        // pf("%lld\n",dp[i]);
        add(dp[i+1],dp[i]*inv[n-i]%mod);
        for(int k=0;i+k+1<=n;k++){
            add(dp[i+k+1],dp[i]*mul[k]%mod*inv[n-i]%mod*ifac[k]%mod);
        }
    }
    ll jc=1;
    rep(i,1,n) jc=jc*i%mod;
    ans=jc*dp[n]%mod;
    pf("%lld\n",ans);
}

标签:组十连测,siz,ll,20zr,day10,操作,frac,prod,dp
From: https://www.cnblogs.com/liyixin0514/p/18435522

相关文章

  • 【20zr提高组十连测day10】信
    【20zr提高组十连测day10】信给定\(n,m\),\(n,m\le10^5\),给定分别长度为\(n-1,m,n,m-1\)的单调不减的序列\(A,B,C,D\),然后形如该图建边:考虑到序列是递增的,对于除最左上角以外的每个点,每个点一定要选和自己相连的一条边才能形成一棵树。那么选择左边或上边一定是更优的,而且......
  • 【20联赛集训day10】排列
    【20联赛集训day10】排列一个长度为\(n\)的排列,每次操作删除所有存在相邻的数字比它更大的数字,问执行\(k\)次操作后剩下恰好一个数的排列方案数。首先因为每次删除至少删一半的数字,所以最多删\(\log\)次。对于一个排列,我们可以发现把序列按最大值劈开,左右两边互不干扰,成......
  • 【20省选十联测day10】Easy Win
    【20省选十联测day10】EasyWin题意原题链接有\(n\)堆石子,\(n\le5\times10^5\),每堆石子有\(a_i\)个,\(a_i\len\)。Alice和Bob每次可以从,某一堆取至少\(1\)颗、至多\(x\)颗石子,不能取的失败。Alice先手,问对于所有\(1\lex\len\),问谁胜利。思路对于一堆石子,计......
  • 数据处理与统计分析篇-day10-Matplotlib数据可视化
    数据可视化简介可视化介绍数据可视化是指直观展现数据,它是数据处理过程的一部分。把数值绘制出来更方便比较。借助数据可视化,能更直观地理解数据,这是直接查看数据表做不到的数据可视化有助于揭示数据中隐藏的模式,数据分析时可以利用这些模式选择模型可视化库介绍基于......
  • c基础day10
    目录[1]递归函数[2]结构体结构体变量赋值访问重命名结构体数组定义初始化结构体数组的输入输出结构体指针结构体大小共用体枚举存储类型[1]递归函数递推:从原问题出发,按递归公式从未知到已知,最终到达递归终止条件回归:按递归的终止条件求出结果,你想逐步带入......
  • Day10.面向对象编程OOP(2)
    封装该露的露,该藏的藏高内聚,低耦合:高内聚:类的内部数据操作细节自己完成,不允许外部干涉低耦合:仅暴露少量的方法给外部使用提高程序的安全性,保护数据隐藏代码的实现细节统一接口提高系统的可维护性packagecom.dongfang.oop.Demo04;//类publicclassDemo01......
  • C++复习day10
    智能指针为什么需要智能指针?#include<iostream>usingnamespacestd;intdiv(){ inta,b; cin>>a>>b; if(b==0) throwinvalid_argument("除0错误"); returna/b;}voidFunc(){ //1、如果p1这里new抛异常会如何? //2、如果p2这里new抛异常会......
  • day10-配置文件&日志&多线程
    一、配置文件1.1properties配置文件properties配置文件特点:1、都只能是键值对2、键不能重复3、文件后缀一般是.properties结尾的​Properties这是一个Map集合(键值对集合),但是我们一般不会当集合使用主要用来代表属性文件,通过Properties可以读写属性文件里的......
  • 【代码随想录Day10】栈与队列Part01
    232.用栈实现队列题目链接/文章讲解/视频讲解:用栈实现队列classMyQueue{Stack<Integer>stackIn;Stack<Integer>stackOut;publicMyQueue(){stackIn=newStack<>();stackOut=newStack<>();}publicvoidpush(int......
  • day10(IO进程)进程间的通信---共享内存
    目录1.特点2.步骤3.函数接口4.命令1.特点1)共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝。2)为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间。进程就可以直接读......