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. 链接脚本再探和VMA与LMA

    链接脚本简单描述 连接脚本的描述都是以节(section)的单位的,网上也有很多描述链接脚本语法的好文章,再不济还有官方的说明文档可以用来学习,其实主要就是对编译构建的整个过程有了深入的理解后就能对链 ...

  2. ::-webkit-scrollbar & hidden scrollbar

    ::-webkit-scrollbar & hidden scrollbar how to hidden ::-webkit-scrollbar *::-webkit-scrollbar { ...

  3. Vue 3.x & v-model

    Vue 3.x & v-model https://v3.vuejs.org/guide/migration/v-model.html#overview BREAKING: When used ...

  4. Google PageSpeed Insights : 网站性能优化检测工具

    1 1 https://developers.google.com/speed/pagespeed/insights/ PageSpeed Insights 使您的网页在所有设备上都能快速加载. 分析 ...

  5. short URL 短网址实现原理剖析

    short URL 短网址实现原理剖析 意义,简短便于分享,避免出现超长 URL 的字符长度限制问题 原理分析, 使用 HashMap 存储对应的映射关系 (长度不超过7的字符串,由大小写字母加数字共 ...

  6. Android 如何使用 WebView 加载 HTML 字符串和处理转译字符

    Android 如何使用 WebView 加载 HTML 字符串和处理转译字符 css bug 在 WebView 中编译 Web 应用 如果您希望在客户端应用中提供 Web 应用(或只是网页),则可 ...

  7. JUnit5学习之一:基本操作

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  8. 彻底理解c++的隐式类型转换

    隐式类型转换可以说是我们的老朋友了,在代码里我们或多或少都会依赖c++的隐式类型转换. 然而不幸的是隐式类型转换也是c++的一大坑点,稍不注意很容易写出各种奇妙的bug. 因此我想借着本文来梳理一遍c ...

  9. django学习-16.返回给前端页面数据为json数据类型的3种方案

    目录结构 1.前言 2.JsonResponse类的源码简单分析 2.1.JsonResponse类的源码如下所示 2.2.JsonResponse类的构造函数里的每个入参的大概含义和作用 3.[方案 ...

  10. [转]LINUX下编译c++11的代码

    转载地址: https://blog.csdn.net/lwb102063/article/details/50445201 C++11,(即ISO/IEC 14882:2011),是目前的C++编程 ...