D

可以发现,从整体上来看这个式子是不好计数的,可以考虑反过来将贡献拆到单个的每个数上:

\[\sum\limits_{i = 1} ^ n a_i \times (i - x) = 0
\]

于是每个数对该式子的贡献都将会是 \(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)\) 还有一个数随便选任何数均可。

基于此可以发现,本质上我们就是要求:

\[\sum\limits_{i = 1} ^ {n - x} a_{i + x} \times i = \sum\limits_{i = 1} ^ {x - 1} a_{x - i} \times i
\]

的方案数,注意到每个数 \(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\) 的方案数,于是有转移:

\[f_{i, j} = \sum\limits_{x = 1} ^ k f_{i - 1, j - ix}
\]

这个转移是一个经典的可以使用模 \(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\) 即可:

\[f_i = \dbinom{i + A_i}{i} - \sum\limits_{j = 1} ^ {i - 1} f_{j - 1} \times \dbinom{A_i - A_j + i - j}{i - 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\) 下方插入一颗子树即可:

\[f_{l, r, x} = \sum\limits_{i = l + 1} ^ r \sum\limits_{j = 1} ^ {x - 1} f_{l, i - 1, x} \times f_{i, r, j} + \sum\limits_{i = l + 1} ^ r \sum\limits_{j = 1} ^ {x - 1} f_{l, i - 1, j} \times f_{i, r, x - 1}
\]

后面的和式枚举到 \(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的更多相关文章

  1. 【题解】Atcoder ARC#90 F-Number of Digits

    Atcoder刷不动的每日一题... 首先注意到一个事实:随着 \(l, r\) 的增大,\(f(r) - f(l)\) 会越来越小.考虑暴力处理出小数据的情况,我们可以发现对于左端点 \(f(l) ...

  2. AtCoder ARC 076E - Connected?

    传送门:http://arc076.contest.atcoder.jp/tasks/arc076_c 平面上有一个R×C的网格,格点上可能写有数字1~N,每个数字出现两次.现在用一条曲线将一对相同的 ...

  3. AtCoder ARC 076D - Built?

    传送门:http://arc076.contest.atcoder.jp/tasks/arc076_b 本题是一个图论问题——Manhattan距离最小生成树(MST). 在一个平面网格上有n个格点, ...

  4. AtCoder ARC 082E - ConvexScore

    传送门:http://arc082.contest.atcoder.jp/tasks/arc082_c 本题是一个平面几何问题. 在平面直角坐标系中有一个n元点集U={Ai(xi,yi)|1≤i≤n} ...

  5. Atcoder ARC 082C/D

    C - Together 传送门:http://arc082.contest.atcoder.jp/tasks/arc082_a 本题是一个数学问题. 有一个长度为n的自然数列a[1..n],对于每一 ...

  6. 【题解】 AtCoder ARC 076 F - Exhausted? (霍尔定理+线段树)

    题面 题目大意: 给你\(m\)张椅子,排成一行,告诉你\(n\)个人,每个人可以坐的座位为\([1,l]\bigcup[r,m]\),为了让所有人坐下,问至少还要加多少张椅子. Solution: ...

  7. 【题解】Atcoder ARC#96 F-Sweet Alchemy

    首先,我们发现每一个节点所选择的次数不好直接算,因为要求一个节点被选择的次数大于等于父亲被选择的次数,且又要小于等于父亲被选择的次数 \(+D\).既然如此,考虑一棵差分的树,规定每一个节点被选择的次 ...

  8. AtCoder ARC 090 E / AtCoder 3883: Avoiding Collision

    题目传送门:ARC090E. 题意简述: 给定一张有 \(N\) 个点 \(M\) 条边的无向图.每条边有相应的边权,边权是正整数. 小 A 要从结点 \(S\) 走到结点 \(T\) ,而小 B 则 ...

  9. 【题解】Atcoder ARC#67 F-Yakiniku Restaurants

    觉得我的解法好简单,好优美啊QAQ 首先想想暴力怎么办.暴力的话,我们就枚举左右端点,然后显然每张购物券都取最大的值.这样的复杂度是 \(O(n ^{2} m)\) 的.但是这样明显能够感觉到我们重复 ...

  10. 【题解】Atcoder ARC#85 E-MUL

    ……没啥可说的.最大权闭合子图,跑下dinic就好了…… #include <bits/stdc++.h> using namespace std; #define maxn 500000 ...

随机推荐

  1. SuperPixel

    目录 SLIC Superpixel algorithm 距离函数的选择 代码 Gonzalez R. C. and Woods R. E. Digital Image Processing (For ...

  2. Normalization Methods

    目录 概 主要内容 Batch Normalization Layer Normalization Instance Normalization Group Normalization Ioffe S ...

  3. SQL Server 数据库添加主键,唯一键,外键约束脚本

    -- 声明使用数据库use 数据库;go -- 添加主键(primary key)约束-- 基本语法-- 判断主键约束是否存在,如果存在则删除,不存在则添加if exists(select * fro ...

  4. Zookeeper集群安装(开启kerberos)

    安装规划 zookeeper集群模式,安装到如下三台机器 10.43.159.237 zdh-237 10.43.159.238 zdh-238 10.43.159.239 zdh-239 Kerbe ...

  5. Zookeeper基础教程(一):认识Zookeeper

    引用百度百科的话 ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件.它是一个为分布式应用提供一致性服 ...

  6. shell3-循环

    常用的循环语句有3种: <1>for <2>while <3>utile 1.for语句的格式: for 变量名 in 列表: do 循环体 done 如何生成列表 ...

  7. 分享一款开源堡垒机-jumpserver

    本文主文章地址为:https://blog.csdn.net/KH_FC JumpServer是由FIT2CLOUD(飞致远)公司旗下一款开源的堡垒机,这款也是全球首款开源的堡垒机,使用 GNU GP ...

  8. pytest用例的执行顺序

    Pytest执行的顺序 当pytest运行测试函数时,它会查看该测试函数中的参数,然后搜索与这些参数具有相同名称的fixture.一旦pytest找到这些对象,它就会运行这些fixture 影响执行顺 ...

  9. Python 元类实现ORM

    ORM概念 ORM(Object Ralational Mapping,对象关系映射)用来把对象模型表示的对象映射到基于 SQL  的关系模型数据库结构中去.这样,我们在具体的操作实体对象的时候,就不 ...

  10. pymysql防止SQL注入的方法

    import pymysql class Db(object): def __init__(self): self.conn = pymysql.connect(host="192.168. ...