CSP复赛day2模拟题

没错,我又爆零了、、、、、先让我自闭一分钟。。。。。so

当你忘记努力的时候,现实会用一记响亮的耳光告诉你东西南北在哪。

好了,现在重归正题:

全国信息学奥林匹克联赛(NOIP2014)

复赛模拟题 Day2

长乐一中

题目名称 改造二叉树 数字对 交换
英文名称 binary pair swap
输入文件名 binary.in pair.in swap.in
输出文件名 binary.out pair.out swap.out
时间限制 1s 2s 1s
空间限制 256M 256M 256M
测试点数目 20 20 10
测试点分值 5 5 10
是否有部分分 无 无 无
题目类型 传统 传统 传统
是否有 SPJ 无 无 无

1.改造二叉树

【题目描述】

小Y在学树论时看到了有关二叉树的介绍:在计算机科学中,二叉树是每个结点最多有
两个子结点的有序树。通常子结点被称作“左孩子”和“右孩子” 。二叉树被用作二叉搜索
树和二叉堆。随后他又和他人讨论起了二叉搜索树。
什么是二叉搜索树呢?二叉搜索树首先是一棵二叉树。设key[p]表示结点p上的数值。
对于其中的每个结点p,若其存在左孩子lch,则key[p]>key[lch];若其存在右孩子rch, 则
key[p]<key[rch];注意,本题中的二叉搜索树应满足对于所有结点,其左子树中的key小于
当前结点的key,其右子树中的key大于当前结点的key。
小Y与他人讨论的内容则是,现在给定一棵二叉树,可以任意修改结点的数值。修改一
个结点的数值算作一次修改,且这个结点不能再被修改。若要将其变成一棵二叉搜索树, 且
任意时刻结点的数值必须是整数(可以是负整数或0) ,所要的最少修改次数。
相信这一定难不倒你!请帮助小Y解决这个问题吧。

【输入格式】
第一行一个正整数 n表示二叉树结点数。结点从 1~n 进行编号。
第二行 n 个正整数用空格分隔开,第 i 个数 ai 表示结点 i 的原始数值。
此后 n - 1 行每行两个非负整数 fa, ch,第 i + 2行描述结点 i + 1 的父亲编号 fa,以及父
子关系 ch,(ch = 0 表示 i + 1 为左儿子,ch = 1表示 i + 1为右儿子)。
结点 1一定是二叉树的根。
【输出格式】
仅一行包含一个整数,表示最少的修改次数。
【样例输入】
3
2 2 2
1 0
1 1
【样例输出】
2
【数据范围】
20 % :n <= 10 , ai <= 100.
40 % :n <= 100 , ai <= 200
60 % :n <= 2000 .
100 % :n <= 10 ^ 5 , ai < 2 ^ 31.第 3页 共 4页

上面那题,我不会啊。。那还是先粘一片正解吧

T1:
20% :暴力。
40% :可以用 DP 或者贪心或者神奇的暴力等其他奇怪的方法完成。
60% :正解的 LIS 打成 O(n ^ 2)。
100% :首先求出这颗二叉树的中序遍历,那么问题就转换成用最少的修改次数使这个整
数序列严格单调递增。于是很自然的想到了 LIS,但单纯用 LIS 是有一些问题的,
比如这种情况:2 3 1 4, LIS 为 2 3 4,答案求出来为 1,但由于整数的限制,应该
要修改 2 次。即直接 LIS 求出的答案是在非严格递增的情况下的答案。
所以我们将原序列稍加修改, 一个常见的将严格递增整数序列映射成非严格递增整
数序列的技巧就是将如下序列:
a1, a2, a3, a4 ... an 映射成:
a1 - 1, a2 - 2, a3 - 3, a4 - 4 ... an - n.
(这种方法常见于计数类问题)。
这样映射后求最长不下降子序列的长度就没问题了。

here:

 1 #include <algorithm>
 2 #include <iostream>
 3 #include <cstring>
 4 #include <cstdio>
 5 #include <cmath>
 6 using namespace std;
 7 
 8 const int N = 1e5 + 3;
 9 int n, fa, d, sum, qr, l, r, mid, top, stk[N], f[N], a[N], b[N], lc[N], rc[N];
10 bool vis[N];
11 
12 char ch;
13 int read() {
14     while (ch = getchar(), ch < '0' || ch > '9');
15     int res = ch - 48;
16     while (ch = getchar(), ch >= '0' && ch <= '9') res = res * 10 + ch - 48;
17     return res;
18 }
19 
20 void Bfs() {
21     int x; stk[top = 1] = 1;
22     while (top) {
23         x = stk[top];
24         if (lc[x] && !vis[lc[x]]) {
25             stk[++top] = lc[x];
26             continue;
27         }
28         b[++sum] = a[x]; b[sum] -= sum;
29         vis[x] = true; --top;
30         if (rc[x] && !vis[rc[x]]) {
31             stk[++top] = rc[x];
32             continue;
33         }
34     }
35     return ;
36 }
37 
38 int main() {
39     freopen("binary.in", "r", stdin);
40     freopen("binary.out", "w", stdout);
41     n = read();
42     for (int i = 1; i <= n; ++i) a[i] = read();
43     for (int i = 2; i <= n; ++i) {
44         fa = read(); d = read();
45         (d ? rc[fa] : lc[fa]) = i;
46     }
47     Bfs();
48     f[qr = 1] = b[1];
49     for (int i = 2; i <= n; ++i) {
50         if (b[i] >= f[qr]) f[++qr] = b[i];
51         else {
52             l = 1; r = qr;
53             while (l <= r) {
54                 mid = l + r >> 1;
55                 if (f[mid] <= b[i]) l = mid + 1;
56                 else     r = mid - 1;
57             }
58             f[l] = b[i];
59         }
60     }
61     cout << n - qr << endl;
62     fclose(stdin); fclose(stdout);
63     return 0;
64 }

 

2.数字对

 

【题目描述】

小 H 是个善于思考的学生,现在她又在思考一个有关序列的问题。
她的面前浮现出一个长度为 n的序列{ai},她想找出一段区间[L, R](1 <= L <= R <= n)。
这个特殊区间满足,存在一个 k(L <= k <= R),并且对于任意的 i(L <= i <= R),ai 都能
被 ak 整除。这样的一个特殊区间 [L, R]价值为 R - L。
小 H 想知道序列中所有特殊区间的最大价值是多少,而有多少个这样的区间呢?这些
区间又分别是哪些呢?你能帮助她吧。


【输入格式】
第一行,一个整数 n.
第二行,n个整数,代表 ai.
【输出格式】
第一行两个整数,num和 val,表示价值最大的特殊区间的个数以及最大价值。
第二行 num 个整数,按升序输出每个价值最大的特殊区间的 L.
【样例输入 1】
5
4 6 9 3 6
【样例输出 1】
1 3
2
【样例输入 2】
5
2 3 5 7 11
【样例输出 2】
5 0
1 2 3 4 5
【数据范围】
30%: 1 <= n <= 30 , 1 <= ai <= 32.
60%: 1 <= n <= 3000 , 1 <= ai <= 1024.
80%: 1 <= n <= 300000 , 1 <= ai <= 1048576.
100%: 1 <= n <= 500000 , 1 <= ai < 2 ^ 31.

说实话,被这题恶心到了,本来信心满满的过了样例,结果爆零

--这告诉我们,你以为你过了只是你以为。。

然后我调这题调了一下午,知道自己哪错了,但就是写不出来 写不出来 不出来。

果断放弃。

T2:
30% :暴力枚举判断。O(n^4)。
60% :特殊区间的特点实际上就是区间最小值等于这个区间的 GCD,于是暴力或递推算
出每个区间的最小值与 GCD。而对于最大价值,可以通过二分来进行求解。复杂
度 O(n ^ 2)。
100%:在 60%的基础上,最小值与 GCD 都使用 RMQ 算法来求解,对于这道题推荐使用
ST 表。最大价值仍然使用二分。复杂度 O(nlogn)。

 1 #include <algorithm>
 2 #include <iostream>
 3 #include <cstring>
 4 #include <cstdio>
 5 #include <cmath>
 6 using namespace std;
 7 
 8 const int N = 5e5 + 3, M = 21;
 9 int n, m, a, ans, l, r, mid, sum, A[N], f[N][M], g[N][M], p[M];
10 
11 inline int Gcd(const int &x, const int &y) {
12     return y == 0 ? x : Gcd(y, x % y);
13 }
14 
15 bool check(int len) {
16     int q = log2(len--), k = n + 1 - p[q], j;
17     for (int i = 1; i <= k; ++i) {
18         j = i + len;
19         if (min(f[i][q], f[j - p[q] + 1][q]) == Gcd(g[i][q], g[j - p[q] + 1][q]))
20             return true;
21     }
22     return false;
23 }
24 
25 char ch;
26 inline int read() {
27     while (ch = getchar(), ch < '0' || ch > '9');
28     int res = ch - 48;
29     while (ch = getchar(), ch >= '0' && ch <= '9') res = res * 10 + ch - 48;
30     return res;
31 }
32 
33 char s[10];
34 inline void print(int x) {
35     int res = 0;
36     if (x == 0) putchar('0');
37     while (x) {
38         s[++res] = x % 10;
39         x /= 10;
40     }
41     for (int i = res; i; --i) putchar(s[i] + '0');
42     putchar(' ');
43     return ;
44 }
45 
46 int main() {
47     freopen("pair.in", "r", stdin);
48     freopen("pair.out", "w", stdout);
49     n = read(); m = log2(n);
50     for (int i = 1; i <= n; ++i) {
51         a = read();
52         f[i][0] = g[i][0] = a;
53     }
54     for (int i = 0; i <= m; ++i) p[i] = 1 << i;
55     for (int j = 1; j <= m; ++j) {
56         int k = n + 1 - p[j];
57         for (int i = 1; i <= k; ++i) {
58             f[i][j] = min(f[i][j - 1], f[i + p[j - 1]][j - 1]);
59             g[i][j] = Gcd(g[i][j - 1], g[i + p[j - 1]][j - 1]);
60         }
61     }
62     l = 1; r = n;
63     while (l <= r) {
64         mid = l + r >> 1;
65         if (check(mid)) l = mid + 1;
66         else     r = mid - 1;
67     }
68     ans = r;
69     if (ans == 1) {
70         printf("%d %d\n", n, 0);
71         for (int i = 1; i < n; ++i)
72             print(i);
73         printf("%d\n", n);
74     }
75     else {
76         int q = log2(ans--), k = n + 1 - p[q], j;
77         for (int i = 1; i <= k; ++i) {
78             j = i + ans;
79             if (min(f[i][q], f[j - p[q] + 1][q]) == Gcd(g[i][q], g[j - p[q] + 1][q]))
80                 A[++sum] = i;
81         }
82         printf("%d %d\n", sum, ans);
83         for (int i = 1; i < sum; ++i)
84             print(A[i]);
85         printf("%d\n", A[sum]);
86     }
87     fclose(stdin); fclose(stdout);
88     return 0;
89 }

 

3.交换

 

【题目描述】

给定一个{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[q0],s[q0 + 1]
2. 交换 s[q1],s[q1 + 1]

最后能使得排列 s = p.
问有多少个优美的排列,答案对 10^9+7 取模。

【输入格式】
第一行一个正整数 n.
第二行 n 个整数代表排列 p.
【输出格式】
仅一行表示答案。
【样例输入】
3
1 2 0
【样例输出】
1
【样例解释】
q = {0,1} {0,1,2} ->{1,0,2} -> {1, 2, 0}
q = {1,0} {0,1,2} ->{0,2,1} -> {2, 0, 1}
【数据范围】
30%: n <= 10
100%: n <= 50

这是我颇为得意的一题--但还是没过--DFS回溯打错了,忘记交换回来了,这告诉我们打DFS在return时不仅要改book[i],还要把其他的改变的也改回来,让它返璞归真,以它该有的样子进行下一个状态的search。

先粘我的代码(经过周同学的“美化”):

 1 #include<iostream>
 2 #include<string>
 3 using namespace std;//n值比较小 用DFS做 
 4 //string 大法好 
 5 // 30分
 6 const int P = 1e9 + 7; //模数可以声明成常量,这样写比较好看,也比较好懂 ,大众平时也这样用 
 7 int n, book[60];
 8 long long ans;
 9 string p, s; //声明全局变量,可以节省内存,也可以避免传参错误,但是这样在不注意的情况下会导致错误 
10 
11 void dfs(int k) { //cnt the number of change
12     if(k == n - 1)  { if(s == p) ans++; return; }
13     for(int i = 0; i <= n - 2; i++) {
14         if(book[i] == 0) {
15             book[i] = 1;
16             swap(s[i], s[i + 1]);
17             dfs(k + 1);
18             swap(s[i], s[i + 1]);//big problem就是这,害我爆零。。还找了半天才找到
19             book[i] = 0;
20         }
21     }
22 }
23 
24 int main() {
25     cin >> n;
26     for(int i = 0; i < n; i++) {
27         char a;
28         cin >> a;
29         p = p + a;
30     }
31     for(int i = 0; i < n; i++) s = s + char('0' + i);
32     dfs(0);
33     cout << ans % P;
34     return 0;
35 }
36 
37 /*
38 大括号要写整齐,
39 代码好不好看大部分取决于大括号的整齐程度,缩进真的无所谓,但大括号一定要整齐 
40 
41 */

正解:

T3:
30%:
枚举所有排列,判定即可。
100%:
考虑倒着处理, 比如交换 (i, i + 1), 那么前面的所有数不管怎么交换都无法到后面去(下
标恒小于等于 i),后面的数也是一样到不了前面。说明这最后一次交换前,就要求对于所有
的 x <= i, y > i,px<py。所以交换前左边的数是连续的,右边也是连续的。由于交换前, 前
面和后面的数是互相不干涉的,所以就归结成了两个子问题。于是我们可以用记忆化搜索来
解决这个问题。
设 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, n – 1 - k)。
即前面的 k 个元素与后面的 n - k 个元素是两个独立的子问题,前面是{low ... low + k - 1}的
排列,后面是{low + k ... low + n - 1}的排列,C(n - 2, n - 1 - k)代表的是在交换(k, k + 1)前左
右两边还分别要进行 n - 2次交换,而每次交换左边与交换右边是不同方案,这相当于 n - 2
个位置选择 n - 1 - k 个位置填入,故还需要乘上 C(n - 2, n - 1 - k)。时间复杂度为 O(n^4)。

 1 #include <algorithm>
 2 #include <iostream>
 3 #include <cstring>
 4 #include <cstdio>
 5 #include <cmath>
 6 using namespace std;
 7 
 8 typedef long long ll;
 9 const int N = 52, Mod = 1e9 + 7;
10 int n, p[N], dp[N][N], C[N][N];
11 
12 int Dfs(int len, int low) {
13     if (dp[len][low] != -1) return dp[len][low];
14     if (len == 1) return dp[len][low] = 1;
15     int &res = dp[len][low]; res = 0;
16     int t[N], m = 0, j, k;
17     for (int i = 1; i <= n; ++i)
18         if (p[i] >= low && p[i] < low + len)
19             t[++m] = p[i];
20     for (int i = 1; i < m; ++i) {
21         swap(t[i], t[i + 1]);
22         for (j = 1; j <= i; ++j)
23             if (t[j] >= low + i) break;
24         for (k = i + 1; k <= m; ++k)
25             if (t[k] < low + i) break;
26         if (j > i && k > m) {
27             ll tmp = (ll)Dfs(i, low) * Dfs(m - i, low + i) % Mod;
28             tmp = tmp * C[m - 2][i - 1] % Mod;
29             res = (res + tmp) % Mod;
30         }
31         swap(t[i], t[i + 1]);
32     }
33     return res;    
34 }
35 
36 int main() {
37     freopen("swap.in", "r", stdin);
38     freopen("swap.out", "w", stdout);
39     scanf("%d", &n);
40     for (int i = 1; i <= n; ++i) scanf("%d", &p[i]);
41     memset(dp, -1, sizeof(dp));
42     for (int i = 0; i <= n; ++i) {
43         C[i][0] = 1;
44         for (int j = 1; j <= i; ++j)
45             C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % Mod;
46     }
47     Dfs(n, 0);
48     if (dp[n][0] != -1) cout << dp[n][0] << endl;
49     else     puts("0");
50     fclose(stdin); fclose(stdout);
51     return 0;
52 }

 

 

 

 

posted @ 2019-11-07 19:38  beca~se  阅读(410)  评论(0编辑  收藏  举报