P10004 [集训队互测 2023] Permutation Counting 2

news/2025/9/24 19:34:51/文章来源:https://www.cnblogs.com/cjoierzdc/p/19109836

把排列写成一条路径 \(p_1\to p_2\to\cdots\to p_n\)。那么 \([p_i<p_{i+1}]\) 就是第 \(i\) 步往右走,\([p^{-1}_i<p^{-1}_{i+1}]\)\(i\) 要先于 \(i+1\) 访问。

如果我们已知了 \(p^{-1}_{i}<p^{-1}_{i+1}\),此时路径形如 \(\cdots\to i\to\cdots\to i+1\to\cdots\)。可以发现,此时我们可以把 \(i\)\(i+1\) 缩在一起,并允许它走两遍。第一次选择 \(i\),第二次选择 \(i+1\)

考虑钦定若干组 \(p^{-1}_{i}<p^{-1}_{i+1}\),此时序列会被缩成 \(k\) 段,长度依次为 \(c_1,c_2,\cdots,c_k\)。因为每段内必须从小往大走,所以其实只需要考虑走过的点所属连续段。

不妨设走过的点所属连续段为 \(v_1,v_2,\cdots,v_n\)。容易发现,此时 \(\sum[p_i<p_{i+1}]\) 相当于 \(\sum[v_i\le v_{i+1}]\)。那么我们想要解决的就是把 \(c_i\)\(i\) 重排,数 \(\sum[v_i\le v_{i+1}]=x\) 的方案数。这个可以 \(O(n^5)\) dp。

$O(n^5)$ 的实现
#include <bits/stdc++.h>
using namespace std;
using ll = long long;int mod;
struct Barrett {__uint128_t t;Barrett(int mod = 2) {t = ((__uint128_t)1 << 64) / mod;}ll operator () (ll x) {x -= ((t * x) >> 64) * mod;return x -= (x >= mod) * mod;}
}MOD;
void Add(int &x, ll y) { x = MOD(x + y); }const int kN = 505;
int n;
int C[kN][kN], mul[kN];
int f[kN][kN][kN], g[kN][kN][kN];
int ans[kN][kN];void InitComb(int N = kN - 1) {mul[0] = 1;for(int i = 1; i <= N; i++) {mul[i] = MOD((ll)mul[i - 1] * i);}for(int i = 0; i <= N; i++) {C[i][0] = 1;for(int j = 1; j <= i; j++) {C[i][j] = MOD(C[i - 1][j] + C[i - 1][j - 1]);}}
}void PreWork() {for(int i = 1; i <= n; i++) {for(int j = 1; i + j <= n + 1; j++) {for(int k = 0; k <= min(i, j); k++) {for(int c = 0; c <= k; c++) {int val = MOD((ll)C[k][c] * C[j - c + i - 1][i]);Add(g[i][j][k], (c & 1) ? mod - val : val);}}}}
}int main() {// freopen("1.in", "r", stdin);// freopen("1.out", "w", stdout);ios::sync_with_stdio(0), cin.tie(0);cin >> n >> mod;MOD = Barrett(mod);InitComb();PreWork();for(int i = 1; i <= n; i++) f[1][i][i - 1] = 1;for(int i = 1; i <= n; i++) {for(int s = 1; s <= n; s++) {for(int j = 0; j <= s; j++) {if(!f[i][s][j]) continue;ll val = f[i][s][j];for(int c = 1; s + c <= n; c++) {for(int t = 0; t <= min(c, j + 1); t++) {ll coe = MOD((ll)C[j + 1][t] * g[c][s - j + t][t]);Add(f[i + 1][s + c][j + c - t], val * coe);}}}}for(int c = 0; c < n; c++) Add(ans[n - i][c], f[i][n][c]);}for(int i = 0; i < n; i++, cout << "\n") {for(int j = 0; j < n; j++) {int sum = 0;for(int k = i; k < n; k++) {int val = MOD((ll)ans[k][j] * C[k][i]);Add(sum, ((k - i) & 1) ? mod - val : val);}cout << sum << " ";}}return 0;
}

实际上可以发现,这样直接 dp 状态数已经是三方了,转移看起来也不太能 \(O(1)\)

对原路径 \(p_1\to\cdots\to p_n\) 分析。此时 \(k\) 段就是 \(k\) 个区间 \([l_i,r_i]\)

考虑从前往后扫,维护相对大小关系。一个想法是 \(f_{i,j,k}\) 表示前 \(i\) 个位置,\(j\) 个区间,钦定 \(k\) 个上升的方案数,每次加入区间。但是这样转移需要枚举新增的递增长度和区间数量,似乎又只能五方。

另一个想法是,枚举区间数 \(c\)。然后 \(f_{i,j}\) 表示前 \(i\) 个位置,钦定 \(j\) 个上升。这样可以总复杂度 \(O(n^4)\) 地 dp 出来。然后容斥一下空区间和把一个区间拆成多个,这样总复杂度也是 \(O(n^4)\)

$O(n^4)$ 的实现
#include <bits/stdc++.h>
using namespace std;
using ll = long long;int mod;
struct Barrett {__uint128_t t;Barrett(int mod = 2) {t = ((__uint128_t)1 << 64) / mod;}ll operator () (ll x) {x -= ((t * x) >> 64) * mod;return x -= (x >= mod) * mod;}
}MOD;
void Add(int &x, ll y) { x = MOD(x + y); }const int kN = 1005;
int n;
int C[kN][kN], mul[kN];
int f[kN][kN];
int ans[kN][kN];void InitComb(int N = kN - 1) {mul[0] = 1;for(int i = 1; i <= N; i++) {mul[i] = MOD((ll)mul[i - 1] * i);}for(int i = 0; i <= N; i++) {C[i][0] = 1;for(int j = 1; j <= i; j++) {C[i][j] = MOD(C[i - 1][j] + C[i - 1][j - 1]);}}
}int main() {// freopen("1.in", "r", stdin);// freopen("1.out", "w", stdout);ios::sync_with_stdio(0), cin.tie(0);cin >> n >> mod;MOD = Barrett(mod);InitComb();for(int c = 1; c <= n; c++) {memset(f, 0, sizeof(f));f[0][0] = 1;for(int i = 0; i < n; i++) {for(int j = 0; j <= i; j++) {ll val = f[i][j];for(int k = 1; k + i <= n; k++) {Add(f[i + k][j + k - 1], val * C[c + k - 1][k]);}}}memcpy(ans[c], f[n], sizeof(ans[c]));}for(int i = n; i; i--) {for(int j = 0; j < n; j++) {int sum = 0;for(int k = i; k; k--) {ll val = MOD((ll)ans[k][j] * C[i][k]);Add(sum, ((i - k) & 1) ? mod - val : val);}ans[i][j] = sum;}}for(int i = n; i; i--) {for(int j = 0; j < n; j++) {int sum = 0;for(int k = i; k; k--) {ll val = MOD((ll)ans[k][j] * C[n - k][i - k]);Add(sum, ((i - k) & 1) ? mod - val : val);}ans[i][j] = sum;}}for(int i = 1; i <= n; i++) {for(int j = 0; j < n; j++) {int sum = 0;for(int k = j; k < n; k++) {ll val = MOD((ll)ans[i][k] * C[k][j]);Add(sum, ((k - j) & 1) ? mod - val : val);}ans[i][j] = sum;}}for(int i = 0; i < n; i++, cout << "\n") {for(int j = 0; j < n; j++) {cout << ans[n - i][j] << " ";}}return 0;
}

之后就是简单的了。这个做法的瓶颈在于对 \(f\) 的 dp。转移是 \(f_{i,j}\times\dbinom{c+k-1}{k}\to f_{i+k,j+k-1}\)

\(g_{i,i-j}=f_{i,j}\),那么转移可以改写成 \(g{i,i-j}\times\dbinom{c+k-1}{k}\to g_{i+k,i-j+1}\)。此时就是卷积的形式。写出转移的 OGF 为 \(F(x)=\sum\limits_{n\ge 1}x^n\dbinom{c+n-1}{n}=\dfrac{1}{(1-x)^c}-1\)

那么我们要算形如 \([x^m]F^n(x)\),拆一下括号即可。总复杂度 \(O(n^3)\)

#include <bits/stdc++.h>
using namespace std;
using ll = long long;int mod;
struct Barrett {__uint128_t t;Barrett(int mod = 2) {t = ((__uint128_t)1 << 64) / mod;}ll operator () (ll x) {x -= ((t * x) >> 64) * mod;return x -= (x >= mod) * mod;}
}MOD;
void Add(int &x, ll y) { x = MOD(x + y); }
int Pow(int x, int y) {int b = x, r = 1;for(; y; y /= 2, b = MOD((ll)b * b)) {if(y & 1) r = MOD((ll)r * b);}return r;
}const int kN = 505, kL = 3e5 + 5;
int n;
int mul[kL], imul[kL], cm[kL];
int C[kN][kN];
int ans[kN][kN];void InitComb(int N = kL - 1, int M = kN - 1) {mul[0] = 1;for(int i = 1; i <= N; i++) {mul[i] = MOD((ll)mul[i - 1] * i);}imul[N] = Pow(mul[N], mod - 2);for(int i = N - 1; ~i; i--) {imul[i] = MOD((ll)imul[i + 1] * (i + 1));}for(int i = 0; i <= M; i++) {C[i][0] = 1;for(int j = 1; j <= i; j++) {C[i][j] = MOD(C[i - 1][j] + C[i - 1][j - 1]);}}
}
int Comb(int n, int m) {if((n < m) || (m < 0)) return 0;return MOD(MOD((ll)mul[n] * imul[m]) * imul[n - m]);
}int main() {// freopen("1.in", "r", stdin);// freopen("1.out", "w", stdout);ios::sync_with_stdio(0), cin.tie(0);cin >> n >> mod;MOD = Barrett(mod);InitComb();for(int i = 0; i < kL; i++) cm[i] = Comb(i, n);for(int c = 1; c <= n; c++) {for(int i = 0; i < n; i++) {for(int j = 0; j <= n - i; j++) {ll val = MOD((ll)C[n - i][j] * cm[c * (n - i - j) + n - 1]);Add(ans[c][i], (j & 1) ? mod - val : val);}}}for(int i = n; i; i--) {for(int j = 0; j < n; j++) {int sum = 0;for(int k = i; k; k--) {ll val = MOD((ll)ans[k][j] * C[i][k]);Add(sum, ((i - k) & 1) ? mod - val : val);}ans[i][j] = sum;}}for(int i = n; i; i--) {for(int j = 0; j < n; j++) {int sum = 0;for(int k = i; k; k--) {ll val = MOD((ll)ans[k][j] * C[n - k][i - k]);Add(sum, ((i - k) & 1) ? mod - val : val);}ans[i][j] = sum;}}for(int i = 1; i <= n; i++) {for(int j = 0; j < n; j++) {int sum = 0;for(int k = j; k < n; k++) {ll val = MOD((ll)ans[i][k] * C[k][j]);Add(sum, ((k - j) & 1) ? mod - val : val);}ans[i][j] = sum;}}for(int i = 0; i < n; i++, cout << "\n") {for(int j = 0; j < n; j++) {cout << ans[n - i][j] << " ";}}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/916147.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java数组拷贝主要有四种方法,浅拷贝

java数组拷贝主要有四种方法,浅拷贝java数组拷贝主要有四种方法,浅拷贝 在Java中,数组拷贝可以通过多种方式实现,每种方式有其特定的用途和性能特点。下面列举四种常见的方法: 1. 使用System.arraycopy() System.…

毕赤酵母细胞工厂升级:CRISPR 技术破局传统局限,解锁多基因代谢工程新可能

在合成生物学与代谢工程的推动下,微生物细胞工厂已成为 bulk 化学品、高价值天然产物及重组蛋白的核心生产平台 —— 其温和的反应条件、高特异性的催化能力,可大幅降低传统化学合成的污染与能耗。在众多微生物宿主中…

日总结 7

今天上课老师让我们讲我们为大作业准备的ppt,详细内容就是介绍自己的产品,每个队3分钟时间,所以ppt的量要少而精,跟我们要介绍产品时一样,要在有限的时间内介绍出自己产品的核心和亮点,同时ppt的制作也有讲究,不…

React学习教程,从入门到精通,React Router 语法知识点及使用手段详解(28)

React学习教程,从入门到精通,React Router 语法知识点及使用手段详解(28)2025-09-24 19:33 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x…

网站制作网站设计沈阳关键字优化公司

目录 一.format 函数简介 1.format 函数不设置下标2.format 函数设置下标 二.format 函数实战三.猜你喜欢 零基础 Python 学习路线推荐 : Python 学习目录 >> Python 基础入门 一.format 函数简介 format 函数主要是用来构造字符串&#xff0c;基本语法是通过 {} 符号操…

读书笔记:OpenPBR 规范(1)

1. 历史背景和动机 ​​​  计算机图形场景资产的转换仍然是一个重大问题,尤其是在表面外观方面。不同的渲染器和3D引擎使用不同的着色系统、着色语言以及固定渲染管线,这些系统不易互相操作。此外,准确建模表面外…

9月24号

今天上午,进行了离散数学的学习。 学习了集合关系和矩阵。 然后进行了马克思思想理论学习。

leetcode(填充每个节点的下一个右侧节点指针 II) - 详解

leetcode(填充每个节点的下一个右侧节点指针 II) - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consola…

做网站的就业前景泰州建设企业网站

为了避免用户在登录同一网站时频频输入账号、密码的现象&#xff0c;谷歌在自家的Chrome浏览器中贴心的加入了“自动填充功能”。那么&#xff0c;谷歌浏览器自动填充功能要怎么开启呢?不清楚操作方法的朋友&#xff0c;不妨收藏学习一下。 方法步骤 在Chrome地址栏输入“ch…

建设网站需要什么技术人员知名的企业网站建设

这个问题在我的电脑上由来已久&#xff0c;但是大部分的更新工作可以由其他第三方软件来完成&#xff0c;所有有时候得过且过。但同时&#xff0c;有一些棘手的问题&#xff0c;会提示系统进行 Windows Update&#xff0c;只有硬着头皮解决了。如果你遇到了“系统无法创建还原点…

湛江网站制作系统摄影素材网站

目的&#xff1a; 物理层要尽可能地屏蔽掉物理设备和传输媒体&#xff0c;通信手段的不同&#xff0c;使数据链路层感觉不到这些差异&#xff0c;只考虑完成本层的协议和服务。 给其服务用户&#xff08;数据链路层&#xff09;在一条物理的传输媒体上传送和接收比特流…

网站后台安全性配置购物商城app建设

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 网络架构与特征提取 4.2 输出表示 4.3损失函数设计 4.4预测阶段 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 load yolov2.mat% 加载…

linux系统下nginx网站ssl证书自动续签

本文使用 acme.sh + 自动续签 + Nginx reload 1.安装 acme.shcurl https://get.acme.sh | shacme.sh 在申请证书时,默认使用 ZeroSSL 作为 CA(证书颁发机构),ZeroSSL 免费额度有限(通常每月 3 张 DV 证书免费),…

C#使用Bitmap操作图像的基础方法

Bitmap概述 Bitmap是C# 中用于处理图像的核心类之一,位于System.Drawing命名空间中。它封装了GDI+位图,允许开发者加载、保存、显示和操作图像文件或内存中的位图资源。Bitmap 类支持多种图像格式,包括 BMP、JPG、P…

icp ip 网站备案网站怎么做定时任务

我们都知道&#xff0c;想要在函数中修改某个变量的值&#xff0c;传变量本身是没有用的。原因在于不同的函数在不同的空间上&#xff0c;函数的生命周期随着函数的调用而结束&#xff0c;因此在函数内部进行的值操作是不会对函数外的变量产生影响的。所以在函数里面想要修改变…

西昌市住房与城乡建设厅网站百度账号找回

图像对任何网站都至关重要,可以增强视觉吸引力和用户体验。但是,图像也会显着影响网站的加载时间,因此必须针对 Web 使用对其进行优化。一种方法是使用正确的图像格式。

知识学报:位运算(1)

不是题解不是教学!!!!! 9.24 洛谷 P4310 题目给定一个长度为 n 的数组,选取一个子序列,使子序列相邻的数之间的 AND 不为 0, 问子序列最长为多少。 很容易想到对于第 i 个数,可以找到之前子序列长度最大的一个…

ThinkPHP在启用nginx反向代理后如何获取真实的Ip地址

ThinkPHP在启用nginx反向代理后如何获取真实的Ip地址pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&quo…

CentOS 7 下 Kubernetes 集群搭建与配置指南

环境准备 系统要求至少3台CentOS 7服务器(1个Master,2个Worker节点)每台服务器至少2GB RAM,2核CPU所有节点间网络互通关闭防火墙和SELinux(生产环境请按需配置)主机规划示例 主机名IP地址角色k8s-master 192.168…

wpf 自定义输入ip地址的文本框 - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …