全国信息学奥林匹克联赛(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.

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.
(这种方法常见于计数类问题)。
这样映射后求最长不下降子序列的长度就没问题了。
View Code
 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 }
View Code

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)。
View Code
 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 }
View Code

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

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)。
View Code 
 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 }
View Code
posted @ 2016-11-15 13:56  J_william  阅读(695)  评论(0编辑  收藏  举报