Atcoder ARC-104
D
可以发现,从整体上来看这个式子是不好计数的,可以考虑反过来将贡献拆到单个的每个数上:
\]
于是每个数对该式子的贡献都将会是 \(a_i \times (i - x)\),此时枚举每个 \(x\) 我们可以轻松地得到一个 \(\mathcal{O(n ^ 4k)}\) 的暴力 \(dp\)。
可以发现,在枚举每个 \(x\) 后我们都重新 \(dp\) 了一遍,可以从此处下手思考一下不同的 \(x\) 会对原式有何影响。
不难发现,实际上 \(x\) 规定了有 \(n - x\) 个数对答案的贡献系数分别为 \(1, 2, \cdots n - x\),有 \(x - 1\) 个数分别对答案的贡献系数为 \(-1, -2, \cdots -(x - 1)\) 还有一个数随便选任何数均可。
基于此可以发现,本质上我们就是要求:
\]
的方案数,注意到每个数 \(a\) 都是等价的,于是可以看作是 \(\sum\limits_{i = 1} ^ {n - x} a_{i} \times i = \sum\limits_{i = 1} ^ {x - 1} a_{i} \times i\) 的方案数。
那么此时每个数的贡献就与 \(x\) 无关了,首先可以 \(dp\) 预处理出 \(\sum\limits_{i = 1} ^ n a_i \times i = x\) 的方案数,然后合并即可。
最简单的 \(dp\) 可以考虑令 \(f_{i, j}\) 表示考虑完前 \(i\) 个数,当前贡献和为 \(j\) 的方案数,于是有转移:
\]
这个转移是一个经典的可以使用模 \(i\) 的同余性用前缀和优化的东西,可以做到 \(\mathcal{O(1)}\) 转移,于是总复杂度为 \(\mathcal{O(n ^ 3k)}\)。
需要注意的是,我们忽略了 \(i = x\) 时贡献系数为 \(0\) 的情况,随便选择任何数均可,同时,因为 \(\sum a_i \ne 0\) 因此总方案还要减 \(1\)。
#include <bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for (int i = l; i <= r; ++i)
const int N = 1e2 + 5;
int n, k, P, ans[N], S[N][N * N], dp[N][N * N * N];
int Inc(int a, int b) { return (a += b) >= P ? a - P : a;}
int Dec(int a, int b) { return (a -= b) < 0 ? a + P : a;}
int Mul(int a, int b) { return 1ll * a * b % P;}
int main() {
cin >> n >> k >> P;
dp[0][0] = 1;
rep(i, 1, n) {
rep(j, 0, i * i * k) {
S[j % i][j / i] = dp[i - 1][j];
if(j / i > 0) S[j % i][j / i] = Inc(S[j % i][j / i], S[j % i][j / i - 1]);
dp[i][j] = S[j % i][j / i];
if(j / i > k) dp[i][j] = Dec(dp[i][j], S[j % i][j / i - k - 1]);
}
}
rep(i, 1, n) rep(j, 0, n * n * k) ans[i] = Inc(ans[i], Mul(dp[n - i][j], dp[i - 1][j]));
rep(i, 1, n) ans[i] = Dec(Mul(ans[i], k + 1), 1);
rep(i, 1, n) printf("%d\n", ans[i]);
return 0;
}
E
可以发现,本题每种情况是等概率的,因此可以将原问题转化为求所有情况的 \(\rm LIS\) 之和。
一个直接的想法就是暴力枚举每个数选择什么,但这样是没有前途的只能换一个方向试试。
注意到每种情况的 \(\rm LIS\) 很小,可以考虑通过一种方式固定 \(\rm LIS\) 然后统计这种情况下的方案数即可。
因为值域巨大,因此只能通过确定这些数的相对关系才能知道 \(\rm LIS\)。
但麻烦的是会出现权值相等的情况,这样一来题目要求的 \(\rm LIS\) 就不好直接求了。
可以考虑调整一下相对关系以做到能求出 \(\rm LIS\) 。
如果不会出现权值相等的情况,那么排名序列的 \(\rm LIS\) 就是原序列的 \(\rm LIS\)。
核心矛盾在于选择排名序列的 \(\rm LIS\) 时可能出现选择了同一权值在排名序列中单调递增的数。
那么就只需要做到选择了同一个权值就不能选择这个权值之后的数即可,不难发现我们只需要将原序列的位置按照从大到小做为第二关键字排序即可。
因此,此时对于任意一个排名顺序,若 \(p_i < p_{i - 1}\) 则 \(a_{i - 1} \le a_i\) 否则 \(a_{i - 1} < a_i\)。
为了方便,我们将不等关系均转化为 \(\le\),那么只需要将有 \(<\) 的一段后缀的上界减 \(1\) 即可。
于是现在的问题转化为每个位置能选择 \([0, A_i]\) 权值范围内的数,要求满足 \(a_{i - 1} \le a_i\) 的序列数量。
不难发现这个问题实质上是 [APIO2016]划艇 的弱化版,直接套用即可做到 \(\mathcal{O(n ^ 3)}\)。
但这个问题权值的范围不存在下界,因此还有一个更加优秀的做法。
首先可以将上界 \(A_i\) 后缀取 \(\min\) 将其转变为不降的序列。
因为 \(a_{i - 1} \le a_i\) 可以考虑 \(a\) 的差分数组 \(d\),那么本质上需要求的就是 \(x_1 + x_2 + \cdots x_n \ge 0, \forall i, x_i \ge 0, \sum\limits_{j = 1} ^ i x_j \le A_i\) 的方案数。
可以考虑使用 \(dp\) 统计答案,令 \(f_i\) 表示考虑完前 \(i\) 个元素满足条件的方案数。
转移时使用容斥转移,为了不重复计数,转移时考虑枚举第一个不合法的位置 \(j\),钦定到其至少前缀为 \(A_j + 1\) 即可:
\]
组合数使用下降幂的方式计算即可,若预处理组合数可以做到 \(\mathcal{O(n ^ 2)}\),因此复杂度 \(\mathcal{O(n!n ^ 3) / O(n!n ^ 2 + n ^ 4)}\)
#include <bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for (int i = l; i <= r; ++i)
#define dep(i, l, r) for (int i = r; i >= l; --i)
const int N = 10 + 5;
const int Mod = 1e9 + 7;
bool book[N];
int n, S, ans, a[N], b[N], p[N], fac[N], inv[N];
int read() {
char c; int x = 0, f = 1;
c = getchar();
while (c > '9' || c < '0') { if(c == '-') f = -1; c = getchar();}
while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
int Inc(int a, int b) { return (a += b) >= Mod ? a - Mod : a;}
int Dec(int a, int b) { return (a -= b) < 0 ? a + Mod : a;}
int Mul(int a, int b) { return 1ll * a * b % Mod;}
int fpow(int a, int b) { int ans = 1; for (; b; a = Mul(a, a), b >>= 1) if(b & 1) ans = Mul(ans, a); return ans;}
int C(int n, int m) {
if(n < m) return 0;
int ans = 1;
rep(i, 1, m) ans = Mul(ans, n - i + 1);
return Mul(ans, inv[m]);
}
int Lis() {
int ans = 0, dp[N];
rep(i, 1, n) {
dp[i] = 1;
rep(j, 1, i - 1) if(p[j] < p[i]) dp[i] = max(dp[i], dp[j] + 1);
ans = max(ans, dp[i]);
}
return ans;
}
int solve() {
rep(i, 1, n) a[i] = b[p[i]];
rep(i, 1, n) if(p[i] > p[i - 1] && i != 1) rep(j, i, n) --a[j];
dep(i, 1, n - 1) a[i] = min(a[i], a[i + 1]);
if(a[1] < 0) return 0;
int dp[N]; dp[0] = 1;
rep(i, 1, n) {
dp[i] = C(i + a[i], i);
rep(j, 1, i - 1) dp[i] = Dec(dp[i], Mul(dp[j - 1], C(a[i] - a[j] + i - j, i - j + 1)));
}
return Mul(dp[n], Lis());
}
void dfs(int x) {
if(x > n) { ans = Inc(ans, solve()); return ;}
rep(i, 1, n) if(!book[i]) book[i] = 1, p[x] = i, dfs(x + 1), book[i] = 0;
}
int main() {
n = read(), S = 1;
rep(i, 1, n) b[i] = read() - 1, S = Mul(S, b[i] + 1);
fac[0] = inv[0] = 1;
rep(i, 1, n) fac[i] = Mul(fac[i - 1], i), inv[i] = fpow(fac[i], Mod - 2);
dfs(1);
printf("%d", Mul(ans, fpow(S, Mod - 2)));
return 0;
}
F
可以发现,这个构成 \(P\) 序列的过程非常类似于单调栈。
即从左至右维护一个由底而上严格单调递减的单调栈,那么每个位置所填的数即为其在单调栈内下方的第一个元素的位置。
但是这个过程是有局限性的,我们很难直接看出 \(i\) 踢出的元素为哪些,因此可以考虑将抽象问题具体化。
可以发现 \((i, P_i)\) 构成了一个二元组,最简单的想法就是 \(i \rightarrow P_i\) 连一条边。
观察此时的图可以发现这恰好构成了一棵树,且以 \(i\) 为根的一颗子树必定是原序列上 \([i, i + sz_i)\) 的一个区间,\(i\) 一定为整颗子树的最大值;若将编号小的节点放在左侧,则 \(i\) 必定踢出其左侧兄弟自上而下最靠右的一条链,又因为父亲节点必然大于儿子节点,因此我们只需保证 \(i\) 大与其左侧兄弟节点即可。
于是问题可以转化为:为求有多少棵大小为 \(n + 1\) 的树,满足 \(0\) 为根节点,且以 \(i\) 为根的一颗子树是原序列上 \([i, i + sz_i)\) 的一个区间,\(i\) 严格大于子树内所有点且不小于其左侧兄弟节点,满足 \(i\) 的权值不大于 \(A_i\) 的方案数。
因此我们肯定需要贪心地让每个点权值尽可能小才能满足权值的限制。
基于此想法,我们呢可以考虑这样一个 \(dp\),令 \(f_{l, r, x}\) 为以 \(l\) 为根的子树内对应原序列 \([l, r]\) 这个区间,点 \(l\) 填的最小权值为 \(x\) 的方案数。
那么转移只需要考虑往 \(l\) 下方插入一颗子树即可:
\]
后面的和式枚举到 \(x - 1\) 是为了避免记重,转移使用前缀和优化即可做到 \(\mathcal{O(n ^ 4)}\)。
#include <bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for (int i = l; i <= r; ++i)
const int N = 1e2 + 5;
const int Mod = 1e9 + 7;
int n, a[N], S[N][N][N], dp[N][N][N];
int read() {
char c; int x = 0, f = 1;
c = getchar();
while (c > '9' || c < '0') { if(c == '-') f = -1; c = getchar();}
while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
int Inc(int a, int b) { return (a += b) >= Mod ? a - Mod : a;}
int Mul(int a, int b) { return 1ll * a * b % Mod;}
int main() {
n = read(), a[1] = ++n;
rep(i, 2, n) a[i] = read();
rep(i, 1, n) {
dp[i][i][1] = 1;
rep(j, 1, n) S[i][i][j] = 1;
}
rep(t, 2, n) rep(l, 1, n) {
int r = l + t - 1; if(r > n) break;
rep(x, 1, min(a[l], n)) {
rep(i, l + 1, r) if(a[i] >= x - 1) dp[l][r][x] = Inc(dp[l][r][x], Mul(dp[l][i - 1][x], S[i][r][x - 1]));
rep(i, l + 1, r) dp[l][r][x] = Inc(dp[l][r][x], Mul(dp[i][r][x - 1], S[l][i - 1][x - 1]));
}
rep(x, 1, n) S[l][r][x] = Inc(S[l][r][x - 1], dp[l][r][x]);
}
printf("%d", S[1][n][n]);
return 0;
}
Atcoder ARC-104的更多相关文章
- 【题解】Atcoder ARC#90 F-Number of Digits
Atcoder刷不动的每日一题... 首先注意到一个事实:随着 \(l, r\) 的增大,\(f(r) - f(l)\) 会越来越小.考虑暴力处理出小数据的情况,我们可以发现对于左端点 \(f(l) ...
- AtCoder ARC 076E - Connected?
传送门:http://arc076.contest.atcoder.jp/tasks/arc076_c 平面上有一个R×C的网格,格点上可能写有数字1~N,每个数字出现两次.现在用一条曲线将一对相同的 ...
- AtCoder ARC 076D - Built?
传送门:http://arc076.contest.atcoder.jp/tasks/arc076_b 本题是一个图论问题——Manhattan距离最小生成树(MST). 在一个平面网格上有n个格点, ...
- AtCoder ARC 082E - ConvexScore
传送门:http://arc082.contest.atcoder.jp/tasks/arc082_c 本题是一个平面几何问题. 在平面直角坐标系中有一个n元点集U={Ai(xi,yi)|1≤i≤n} ...
- Atcoder ARC 082C/D
C - Together 传送门:http://arc082.contest.atcoder.jp/tasks/arc082_a 本题是一个数学问题. 有一个长度为n的自然数列a[1..n],对于每一 ...
- 【题解】 AtCoder ARC 076 F - Exhausted? (霍尔定理+线段树)
题面 题目大意: 给你\(m\)张椅子,排成一行,告诉你\(n\)个人,每个人可以坐的座位为\([1,l]\bigcup[r,m]\),为了让所有人坐下,问至少还要加多少张椅子. Solution: ...
- 【题解】Atcoder ARC#96 F-Sweet Alchemy
首先,我们发现每一个节点所选择的次数不好直接算,因为要求一个节点被选择的次数大于等于父亲被选择的次数,且又要小于等于父亲被选择的次数 \(+D\).既然如此,考虑一棵差分的树,规定每一个节点被选择的次 ...
- AtCoder ARC 090 E / AtCoder 3883: Avoiding Collision
题目传送门:ARC090E. 题意简述: 给定一张有 \(N\) 个点 \(M\) 条边的无向图.每条边有相应的边权,边权是正整数. 小 A 要从结点 \(S\) 走到结点 \(T\) ,而小 B 则 ...
- 【题解】Atcoder ARC#67 F-Yakiniku Restaurants
觉得我的解法好简单,好优美啊QAQ 首先想想暴力怎么办.暴力的话,我们就枚举左右端点,然后显然每张购物券都取最大的值.这样的复杂度是 \(O(n ^{2} m)\) 的.但是这样明显能够感觉到我们重复 ...
- 【题解】Atcoder ARC#85 E-MUL
……没啥可说的.最大权闭合子图,跑下dinic就好了…… #include <bits/stdc++.h> using namespace std; #define maxn 500000 ...
随机推荐
- AT-GAN: A Generative Attack Model for Adversarial Transferring on Generative Adversarial Nets
目录 概 主要内容 符号说明 Original Generator Transfer the Generator Wang X., He K., Guo C., Weinberger K., Hopc ...
- 后缀数组【原理+python代码】
后缀数组 参考:https://blog.csdn.net/a1035719430/article/details/80217267 https://blog.csdn.net/YxuanwKeith ...
- 后缀树的建立-Ukkonen算法
参考: Ukkonen算法讲解 Ukkonen算法动画 Ukkonen算法,以字符串abcabxabcd为例,先介绍一下运算过程,最后讨论一些我自己的理解. 需要维护以下三个变量: 当前扫描位置# 三 ...
- node.js安装及环境配置超详细教程【Windows系统安装包方式】
文章目录 Step1:下载安装包 Step2:安装程序 Step3:查看 Step4:环境配置 最后补充: Step1:下载安装包 https://nodejs.org/zh-cn/download/ ...
- JSP的数据从数据库中获取导入type=date的input标签中
jsp 页面最开始加上 <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> & ...
- SpringBoot 与 SpringCloud 的版本对应详细信息
"spring-cloud": { "Finchley.M2": "Spring Boot >=2.0.0.M3 and <2.0.0.M ...
- CSS基础 清除浮动
1.单伪元素清除法:清除浮动 .clearfix::after{ content: '.'; display: block; c ...
- Spark-local本地环境搭建
注:图片如果损坏,点击文章链接:https://www.toutiao.com/i6815385772254822919/ 承接上一个文档<Spark源码编译> 解压spark编译好的压缩 ...
- Keil MDK STM32系列(三) 基于标准外设库SPL的STM32F407开发
Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...
- docker创建mysql容器时挂载文件路径后无法启动(已解决)
系统centos7 docker版本: 解决方法: 在docker run中加入 --privileged=true 给容器加上特定权限,如下 docker run --privileged=tru ...