Tag

计数+LIS, 二分+ST表, 计数+记搜

A. 改造二叉树

Description

题面

Solution

如果目标序列非严格递增,或者说目标序列是不下降的,那么答案就是 \(n\) 减去最长不下降子序列的长度。

比如这种情况:\(2\ 3\ 1\ 4\),\(LIS\) 为 \(2\ 3\ 4\),答案求出来为 \(1\),但由于整数的限制,应该要修改 \(2\) 次。即直接 \(LIS\) 求出的答案是在非严格递增的情况下的答案。

现在要求目标序列严格递增,一个常见的将严格递增整数序列映射成非严格递增整数序列的技巧就是将如下序列:

\(a_1, a_2, a_3, a_4 ... a_n\)

映射成:

\(a_1 - 1, a_2 - 2, a_3 - 3, a_4 - 4 ... a_n - n\)

这种方法常见于计数类问题。

这样映射后求最长不下降子序列的长度就没问题了。

考虑证明这个做法的正确性,如果 \(O(n^2)\) 转移 \(LIS\) 的话,每次从位置 \(i\) 转移到位置 \(j\) 时,只有满足 \(a[j]-a[i] \geq j-i\) 才能转移,而整体减去 \(\{1, 2, 3, ..., n\}\) 正是对这个限制的体现。

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std; const int N = 1e5 + 3;
int n, fa, d, sum, qr, l, r, mid, top, stk[N], f[N], a[N], b[N], lc[N], rc[N];
bool vis[N]; char ch;
int read() {
while (ch = getchar(), ch < '0' || ch > '9');
int res = ch - 48;
while (ch = getchar(), ch >= '0' && ch <= '9') res = res * 10 + ch - 48;
return res;
} void Bfs() {
int x; stk[top = 1] = 1;
while (top) {
x = stk[top];
if (lc[x] && !vis[lc[x]]) {
stk[++top] = lc[x];
continue;
}
b[++sum] = a[x]; b[sum] -= sum;
vis[x] = true; --top;
if (rc[x] && !vis[rc[x]]) {
stk[++top] = rc[x];
continue;
}
}
return ;
} int main() {
freopen("binary.in", "r", stdin);
freopen("binary.out", "w", stdout);
n = read();
for (int i = 1; i <= n; ++i) a[i] = read();
for (int i = 2; i <= n; ++i) {
fa = read(); d = read();
(d ? rc[fa] : lc[fa]) = i;
}
Bfs();
f[qr = 1] = b[1];
for (int i = 2; i <= n; ++i) {
if (b[i] >= f[qr]) f[++qr] = b[i];
else {
l = 1; r = qr;
while (l <= r) {
mid = l + r >> 1;
if (f[mid] <= b[i]) l = mid + 1;
else r = mid - 1;
}
f[l] = b[i];
}
}
cout << n - qr << endl;
fclose(stdin); fclose(stdout);
return 0;
}

B. 数字对

Description

题面

Solution 1

二分区间长度,枚举左端点,显然可能的 \(a_k\) 就是区间最小值,判断 \(a_k\) 是否能整除这个区间的所有数就是判断 \(a_k\) 是否与这个区间的 \(gcd\) 相等,\(ST\)表维护区间最小值和区间\(gcd\)。

#include <cstdio>
#include <cmath> const int N = 500005;
int f[N][21], g[N][21], ans[N], n; //区间最小值,区间gcd int read() {
int x = 0; char c = getchar();
while (c < '0' || c > '9') c = getchar();
while (c >= '0' && c <= '9') {
x = (x << 3) + (x << 1) + (c ^ 48);
c = getchar();
}
return x;
}
int min(int x, int y) {
return x < y ? x : y;
}
int gcd(int a, int b) {
return !b ? a : gcd(b, a % b);
}
int Min(int l, int r) {
int k = log2(r - l + 1);
return min(f[l][k], f[r-(1<<k)+1][k]);
}
int Gcd(int l, int r) {
int k = log2(r - l + 1);
return gcd(g[l][k], g[r-(1<<k)+1][k]);
}
bool check(int k) {
int sta[N] = {}, top = 0;
for (int i = 1; i + k - 1 <= n; ++i)
if (Min(i, i + k - 1) == Gcd(i, i + k - 1))
sta[++top] = i;
if (!top) return false;
ans[0] = top;
for (int i = 1; i <= top; ++i) ans[i] = sta[i];
return true;
} int main() {
freopen("pair.in", "r", stdin);
freopen("pair.out", "w", stdout); n = read();
for (int i = 1; i <= n; ++i) f[i][0] = g[i][0] = read();
for (int j = 1; j <= 19; ++j)
for (int i = 1; i + (1 << j) - 1 <= n; ++i) {
f[i][j] = min(f[i][j-1], f[i+(1<<j-1)][j-1]);
g[i][j] = gcd(g[i][j-1], g[i+(1<<j-1)][j-1]);
}
int l = 0, r = n;
while (l < r) { //二分区间长度
int mid = l + (r - l + 1 >> 1);
if (check(mid)) l = mid;
else r = mid - 1;
}
printf("%d %d\n", ans[0], l - 1);
for (int i = 1; i <= ans[0]; ++i) printf("%d ", ans[i]); fclose(stdin);
fclose(stdout);
return 0;
}

Solution 2

然后还跟 \(Asia\) 学了一个除排序以外 \(O(n)\) 的做法…… Orz

大体思路是:

先从小到大排序,然后一个一个作为 \(a_k\) 向左右两边拓展

拓展到的点打上标记,表示不会从这个点开始拓展

这样的话每个点最多只会被它左边或者右边的一个点拓展到,所以复杂度是 \(O(n)\) 的

为什么是 \(O(n)\) 的呢,考虑三个位置 \(x,y,z\),\(y \leq x, z = x * y\),现用 \(x,y\) 去拓展 \(z\),如果 \(x\) 是 \(y\) 的倍数,那么 \(x\) 会被 \(y\) 拓展到,也就不能再去拓展 \(z\) 了;如果 \(x\) 不是 \(y\) 的倍数,\(x\) 拓展到 \(y\) 就会停止,也不会去拓展 \(z\)。

由此可知,每个元素只会被它左边的一个点和它右边的一个点拓展到,所以除排序外的复杂度为 \(O(n)\)。

C. 交换

Description

给定一个 \(\{0, 1, 2, 3, … , n - 1\}\) 的排列 \(p\)。一个 \(\{0, 1, 2 , … , n - 2\}\) 的排列 \(q\) 被认为是优美的排列,当且仅当 \(q\) 满足下列条件:

对排列 \(s = \{0, 1, 2, 3, ..., n - 1\}\) 进行 \(n – 1\) 次交换。

  1. 交换 \(s[q_0],s[q_0 + 1]\)
  2. 交换 \(s[q_1],s[q_1 + 1]\)

最后能使得排列 \(s = p\)。

问有多少个优美的排列,答案对 \(10^9+7\) 取模。\(n \leq 50\)

Solution

一个很厉害的计数\(DP\)。

考虑倒着处理, 比如交换 \((i, i + 1)\), 那么前面的所有数不管怎么交换都无法到后面去,后面的数也是一样到不了前面。说明这最后一次交换前,就要求对于所有的 \(x <= i, y > i\),\(p_x<p_y\)。所以交换前左边的数是连续的,右边也是连续的。由于交换前,前面和后面的数是互相不干涉的,所以就归结成了两个子问题。于是我们可以用记忆化搜索来解决这个问题。

设 \(dp[n][low]\) 代表长度为 \(n\),\(H\) 是 \(\{low, low + 1,…,low + n - 1\}\) 的排列,且 \(H\) 是 \(p\) 的子序列,在 \(H\) 上优美序列的个数。

我们枚举交换哪两个相邻元素 \((k,k+1)\), 然后判断 \(k\) 前面的所有数是否都小于后面的所有数,如果是则进行转移

\[dp[n][low] += dp[k][low] * dp[n – k][low + k ] * C(n – 2, k - 1)
\]

即前面的 \(k\) 个元素与后面的 \(n - k\) 个元素是两个独立的子问题,前面是 \(\{low ... low + k - 1\}\) 的排列,后面是 \(\{low + k ... low + n - 1\}\) 的排列,\(C(n - 2, k - 1)\) 代表的是在交换 \((k, k + 1)\) 前左右两边一共还要进行 \(n - 2\) 次交换,而每次交换左边与交换右边是不同方案,这相当于 \(n - 2\) 个位置选择 \(k - 1\) 个位置填入,故还需要乘上 \(C(n - 2, k - 1)\)。

时间复杂度为 \(O(n^4)\)。

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std; typedef long long ll;
const int N = 52, Mod = 1e9 + 7;
int n, p[N], dp[N][N], C[N][N]; int Dfs(int len, int low) {
if (dp[len][low] != -1) return dp[len][low];
if (len == 1) return dp[len][low] = 1;
int &res = dp[len][low]; res = 0;
int t[N], m = 0, j, k;
for (int i = 1; i <= n; ++i)
if (p[i] >= low && p[i] < low + len)
t[++m] = p[i];
for (int i = 1; i < m; ++i) {
swap(t[i], t[i + 1]);
for (j = 1; j <= i; ++j)
if (t[j] >= low + i) break;
for (k = i + 1; k <= m; ++k)
if (t[k] < low + i) break;
if (j > i && k > m) {
ll tmp = (ll)Dfs(i, low) * Dfs(m - i, low + i) % Mod;
tmp = tmp * C[m - 2][i - 1] % Mod;
res = (res + tmp) % Mod;
}
swap(t[i], t[i + 1]);
}
return res;
} int main() {
freopen("swap.in", "r", stdin);
freopen("swap.out", "w", stdout);
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%d", &p[i]);
memset(dp, -1, sizeof(dp));
for (int i = 0; i <= n; ++i) {
C[i][0] = 1;
for (int j = 1; j <= i; ++j)
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % Mod;
}
Dfs(n, 0);
if (dp[n][0] != -1) cout << dp[n][0] << endl;
else puts("0");
fclose(stdin); fclose(stdout);
return 0;
}

「NOIP模拟赛」Round 3的更多相关文章

  1. 「NOIP模拟赛」Round 2

    Tag 递推,状压DP,最短路 A. 篮球比赛1 题面 \(Milky\ Way\)的代码 #include <cstdio> const int N = 2000, xzy = 1e9 ...

  2. 「NOIP模拟赛」数位和乘积(dp,高精)

    统计方案数,要么组合数,要么递推(dp)了. 这是有模拟赛历史以来爆炸最狠的一次 T1写了正解,也想到开long long,但是开错了地方然后数组开大了结果100->0 T3看错题本来简单模拟又 ...

  3. 「CSP-S模拟赛」2019第四场

    「CSP-S模拟赛」2019第四场 T1 「JOI 2014 Final」JOI 徽章 题目 考场思考(正解) T2 「JOI 2015 Final」分蛋糕 2 题目 考场思考(正解) T3 「CQO ...

  4. 「CSP-S模拟赛」2019第三场

    目录 T1 「POI2007」山峰和山谷 Ridges and Valleys 题目 考场思路(几近正解) 正解 T2 「JOI 2013 Final」 现代豪宅 题目 考场思路(正解) T3 「SC ...

  5. 【模拟】HHHOJ#251. 「NOIP模拟赛 伍」高精度

    积累模拟经验 题目描述 维护一个二进制数,支持如下操作 "+" 该数加 11 "-" 该数减 11 "*" 该数乘 22 "\&q ...

  6. Solution -「牛客 NOIP 模拟赛」打拳

    \(\mathcal{Description}\)   现 \(2^n\) 个人进行淘汰赛,他们的战力为 \(1\sim 2^n\),战力强者能战胜战力弱者,但是战力在集合 \(\{a_m\}\) 里 ...

  7. 「CSP-S模拟赛」2019第二场

    目录 T1 Jam的计数法 题目 考场思路(正解) T2 「TJOI / HEOI2016」排序 题目 考场思路(假正解) 正解 T3 「THUWC 2017」随机二分图 题目 考场思路 正解 这场考 ...

  8. 「CSP-S模拟赛」2019第一场

    目录 T1 小奇取石子 题目 考场思路 正解 T2 「CCO 2017」专业网络 题目 考场思路 题解 T3 「ZJOI2017」线段树 题目 考场思路 正解 这场考试感觉很奇怪. \(T1.T2\) ...

  9. 「2018-11-05模拟赛」T5 传送机 解题报告

    5.传送机(sent.*) 问题描述: 黄黄同学要到清华大学上学去了.黄黄同学很喜欢清华大学的校园,每次去上课时总喜欢把校园里面的每条路都走一遍,当然,黄黄同学想每条路也只走一遍. 我们一般人很可能对 ...

随机推荐

  1. scu-4440 rectangle (非原创)

    Rectangle frog has a piece of paper divided into nn rows and mm columns. Today, she would like to dr ...

  2. favicon.ico All In One

    favicon.ico All In One link rel="icon" type="image/x-icon" href="http://exa ...

  3. 树莓派 4B 入门教程

    树莓派 4B 入门教程 Raspberry Pi, Raspberry Pi 3B, Raspberry Pi 4B 树莓派 4B 入门手册 PDF Raspberry Pi Beginners Gu ...

  4. auto open Chrome DevTools in the command line

    auto open Chrome DevTools in the command line --auto-open-devtools-for-tabs # macOS $ /Applications/ ...

  5. js 大数计算

    js 大数计算 原理 JavaScript 安全整数 是 -253-1 ~ 253-1 ,即: -9007199254740991 ~ 9007199254740991; 换句话说,整数超过这个范围就 ...

  6. js 实现各种数据结构 APP

    js 实现各种数据结构 APP 常见数据结构: 数组,队列,栈,堆,链表,集合,字典,散列表,树, 图 Array, Queue, Link, Collection, Set,Map, HashMap ...

  7. sublime 使用过程中遇到的问题

    1.当我把鼠标放置在下图所示的class上几秒钟后,sublime就会在全局查找当前的class字符,这时sublime就会出现卡顿或无响应 解决方法: 点击preferences下的settings ...

  8. MySQL 修改数据表

    修改数据表: 创建数据表 更改表明 更改字段数据类型 更改字段名称 更改字段名称和数据类型 为表添加新字段 将字段顺序改为第一位 将字段顺序改为另一个字段之后 删除字段 1 use test; 2 3 ...

  9. 模拟web服务器 (小项目) 搭建+部署

    模拟web服务器:可以从浏览器中访问到自己编写的服务器中的资源,将其资源显示在浏览器中. 技术选型: corejava 线程池 同任务并发执行 IO流 传递数据 客户端也会像服务端发送数据, 服务器像 ...

  10. 微信的两种access_token总结,不能混淆

    大家需要弄清楚微信的网页授权token和公众号api调用授权token. 1.网页授权access_token 1.有效期:7200ms 2.微信网页授权是通过OAuth2.0机制实现的,在用户授权给 ...