CSPS前最后一次模拟赛----爆炸的全过程
DAY -2 T1 数字对
题目描述
她的面前浮现出一个长度为 n 的序列{ai},她想找出一段区间[L, R](1≤L≤R≤n)。
这个特殊区间满足,存在一个 k(L≤k≤R),并且对于任意的 i(L≤i≤R),ai 都能被 ak 整除。这样的一个特殊区间 [L, R]价值为 R - L。
小 H 想知道序列中所有特殊区间的最大价值是多少,而有多少个这样的区间呢?这些区间又分别是哪些呢?你能帮助她吧。
输入
第2行:n 个整数,代表 ai.
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<231.
输出
第2行:num个整数,按升序输出每个价值最大的特殊区间的 L
样例输入 Copy
5
4 6 9 3 6
样例输出 Copy
1 3
2
题解:
据说这题很水,各位大佬用了各种各样的方法都能卡,考完之后,感觉就我最憨憨,各位大佬都在搞什么ST表和线段树,就我一个瞎搞了一个算法,每次找一个点,向左向右找出最大拓展的距离,瞎搞一下,打一个类似于记忆化的东西就过了。
代码也很水
1 #include <iostream>
2 #include <cstdio>
3 #include <cstring>
4 #include <algorithm>
5 #include <queue>
6 using namespace std;
7 const int maxn = 1e6 + 10;
8 int n, a[maxn];
9 int ans[maxn], mx, l[maxn], r[maxn], vis[maxn];
10 inline int read()
11 {
12 int x = 0, f = 1;
13 char c = getchar();
14 for (; !isdigit(c); c = getchar())
15 if (c == '-')
16 f = -1;
17 for (; isdigit(c); c = getchar())
18 x = x * 10 + c - '0';
19 return x * f;
20 }
21 void getl(int x)
22 {
23 int cur = x;
24 while (cur >= 1 && a[cur] % a[x] == 0)
25 {
26 l[x] = l[cur];
27 cur = l[cur] - 1;
28 }
29 }
30 void getr(int x)
31 {
32 int cur = x;
33 while (cur <= n && a[cur] % a[x] == 0)
34 {
35 r[x] = r[cur];
36 cur = r[cur] + 1;
37 }
38 }
39 int main()
40 {
41 n = read();
42 for (int i = 1; i <= n; i++)
43 {
44 a[i] = read();
45 }
46 for (int i = 1; i <= n; i++)
47 {
48 l[i] = i;
49 getl(i);
50 }
51 for (int i = n; i >= 1; i--)
52 {
53 r[i] = i;
54 getr(i);
55 }
56 for (int i = 1; i <= n; i++)
57 {
58 ans[i] = r[i] - l[i];
59 if (ans[i] > mx)
60 {
61 mx = ans[i];
62 }
63 }
64 int num = 0;
65 for (int i = 1; i <= n; i++)
66 {
67 if (ans[i] == mx && !vis[l[i]])
68 {
69 num++;
70 vis[l[i]] = 1;
71 }
72 }
73 memset(vis, 0, sizeof(vis));
74 printf("%d %d\n", num, mx);
75 for (int i = 1; i <= n; i++)
76 {
77 if (ans[i] == mx && !vis[l[i]])
78 {
79 printf("%d ", l[i]);
80 vis[l[i]] = 1;
81 }
82 }
83 return 0;
84 }
T2 交换
题目描述
一个{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 取模。
题解:https://www.cnblogs.com/J-william/p/6065429.html
题解:https://blog.csdn.net/qq_41958841/article/details/83338516
输入
第2行:n个整数代表排列 p.
30%的数据: n≤10;
100%的数据: n≤50;
输出
样例输入 Copy
3
1 2 0
样例输出 Copy
1
提示
q = {1,0} {0,1,2} ->{0,2,1} -> {2, 0, 1}
1 #include <iostream>
2 using namespace std;
3 int n, ans;
4 const int maxn = 51;
5 const int mod = 1e9 + 7;
6 int vis[maxn];
7 int p[maxn], q[maxn], s[maxn];
8 int check()
9 {
10 for (int i = 0; i < n; i++)
11 {
12 s[i] = i;
13 }
14 for (int i = 0; i < n - 1; i++)
15 {
16 swap(s[q[i]], s[q[i] + 1]);
17 }
18 for (int i = 0; i < n; i++)
19 {
20 if (s[i] != p[i])
21 {
22 return false;
23 }
24 }
25 return true;
26 }
27 void dfs(int x)
28 {
29 if (x == n - 1)
30 {
31 if (check())
32 {
33 ans++;
34 }
35 return;
36 }
37 for (int i = 0; i < n; i++)
38 {
39 if (!vis[i])
40 {
41 vis[i] = 1;
42 q[x] = p[i];
43 dfs(x + 1);
44 vis[i] = 0;
45 }
46 }
47 }
48 int main()
49 {
50 cin >> n;
51 for (int i = 0; i < n; i++)
52 {
53 cin >> p[i];
54 }
55 dfs(0);
56 cout << ans % mod << endl;
57 return 0;
58 }
这题的正解还是比较难搞的。
倒着处理一下,
然后开始理性化思考(不要让我证明),假设我们换的k和k+1,那其他的一定都是满足好条件的,按照顺序来,前面的无法跑到后面,后面也同样没发跑到前边,也就意味着在最后一次交换前,K左边是连续的,K右边也合适连续的,左边还一定比右边小一点。
然后这就变成了一个两个区间的问题,就可以去搞搞DP或者记忆化搜索之列的了,然后我就写了一个巨像DP的记忆化。
设DP[ i ][ j ]表示第一个(最小的)是j,长度为i的一个序列中,优美序列的个数,且要保证,这个序列是p的子序列
然后去枚举那个元素要和相邻的元素交换。假设前面的所有数,都比后面的所有数小,就开始转移了,然后就开始了我们鬼畜的方程式环节。
先给个式子,如果你看懂了,你就可以关了题解写代码AK了
dp[ i ][ j ] += dp[ k ][ j ] * dp[ i – k ][ j + k ] * C( i – 2, i – 1 - k )。
如果看不懂,那就接着看
我们已经知道前K个元素和后i-k个元素是相对独立的问题,
前面最理想的排列是{j,j+1 j+2,.....j+k-1},后面的是{j+k,j+k+1,.....,j+n-1}
而后面那个C就是一个组合数的东西,表示在交换(K,K+1)前,左右两边还要换i-2次,而每次换右边和换左边是不同的方案,这就等于是在i-2个位置上,去找n-1-k个位置插入东西,所以还有在方程式后面乘一下,这个预处理一下,总算法大概是O(N^4)的,应该不会T;
满分代码
1 #include <iostream>
2 #include <cstdio>
3 #include <cstring>
4 using namespace std;
5 const int maxn = 60;
6 const int mod = 1e9 + 7;
7 int n, p[maxn];
8 int dp[maxn][maxn];
9 int C[maxn][maxn];
10 inline int read()
11 {
12 int x = 0, f = 1;
13 char c = getchar();
14 for (; !isdigit(c); c = getchar())
15 if (c == '-')
16 f = -1;
17 for (; isdigit(c); c = getchar())
18 x = x * 10 + c - '0';
19 return x * f;
20 }
21 int dfs(int len, int minn)
22 {
23 if (dp[len][minn] != -1)
24 {
25 return dp[len][minn];
26 }
27 if (len == 1)
28 {
29 return dp[len][minn] = 1;
30 }
31 int &res = dp[len][minn];
32 res = 0;
33 int t[maxn];
34 int m = 0, j, k;
35 for (int i = 1; i <= n; ++i)
36 {
37 if (p[i] >= minn && p[i] < minn + len)
38 {
39 t[++m] = p[i];
40 }
41 }
42 for (int i = 1; i < m; ++i)
43 {
44 swap(t[i], t[i + 1]);
45 for (j = 1; j <= i; ++j)
46 {
47 if (t[j] >= minn + i)
48 {
49 break;
50 }
51 }
52 for (k = i + 1; k <= m; ++k)
53 {
54 if (t[k] < minn + i)
55 {
56 break;
57 }
58 }
59 if (j > i && k > m)
60 {
61 long long tmp = (long long )dfs(i, minn) * dfs(m - i, minn + i) % mod;
62 tmp = tmp * C[m - 2][i - 1] % mod;
63 res = (res + tmp) % mod;
64 }
65 swap(t[i], t[i + 1]);
66 }
67 return res;
68 }
69 int main()
70 {
71 n = read();
72 for (int i = 1; i <= n; i++)
73 {
74 p[i] = read();
75 }
76 memset(dp, -1, sizeof(dp));
77 for (int i = 0; i <= n; i++)
78 {
79 C[i][0] = 1;
80 for (int j = 1; j <= i; j++)
81 {
82 C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
83 }
84 }
85 dfs(n, 0);
86 if (dp[n][0] != -1)
87 {
88 printf("%d\n", dp[n][0]);
89 }
90 else
91 {
92 printf("0\n");
93 }
94 return 0;
95 }
T3 改造二叉树
题目描述
二叉堆。随后他又和他人讨论起了二叉搜索树。
什么是二叉搜索树呢?二叉搜索树首先是一棵二叉树。设key[p]表示结点p上的数值。对于其中的每个结点p,若其存在左孩子lch,则key[p]>key[lch];若其存在右孩子rch,则
key[p]<key[rch];注意,本题中的二叉搜索树应满足对于所有结点,其左子树中的key小于当前结点的key,其右子树中的key大于当前结点的key。
小Y与他人讨论的内容则是,现在给定一棵二叉树,可以任意修改结点的数值。修改一个结点的数值算作一次修改,且这个结点不能再被修改。若要将其变成一棵二叉搜索树,且任意
时刻结点的数值必须是整数(可以是负整数或0),所要的最少修改次数。
相信这一定难不倒你!请帮助小Y解决这个问题吧。
题解:https://www.cnblogs.com/J-william/p/6065429.html
题解:https://blog.csdn.net/qq_41958841/article/details/83338516
输入
第2行:n 个正整数用空格分隔开,第 i 个数 ai 表示结点 i 的原始数值。
接下来n - 1行:每行两个非负整数 fa, ch,第 i + 2 行描述结点 i + 1 的父亲编号 fa,以及父子关系 ch,(ch = 0 表示 i + 1 为左儿子,ch = 1 表示 i + 1 为右儿子)。结点1一定是二叉树的根。
20%的数据:n≤10 , ai≤100.
40%的数据:n≤100 , ai≤200.
60%的数据:n≤2000 .
100%的数据:n≤105 , ai<231.
输出
样例输入 Copy
3
2 2 2
1 0
1 1
样例输出 Copy
2
思路:
这是一个套路题!!这是一个套路题!!这是一个套路题!!
这题看上去好熟悉,总感觉在哪看到过,感觉这一类题目就是套路,背掉就好
求出中序遍历,问题变成了用最少的修改让这个序列单调递增,然后基本上就会想到LIS,但是题目竟然非常万恶的说了句整数,这就到时单纯的LIS会出事,就想下面这个东西
1 6 4 7
一般就把4改成小数就行了,但他要整数,然后就GG了,但似乎还有救,这个答案是不严格的单调递增的答案,然后我这要把原来的严格单调递增序列映射成费非格单调递增的就好了,然后这是不知道从哪搞到的小方法
原 s1 s2 s3 s4 s5 ....sn
映射 s1-1 s2-2 s3-3 s4-4 s5-5 sn-n
好像是在哪个计数问题里看到的。
然后求最长不下降子序列长度就可以了
感觉这题还好
放下代码
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 const int inf = 2e9; 7 const int maxn = 1e6 + 10; 8 int n, ans, cnt, vis[maxn], a[maxn], g[maxn], val[maxn], ls[maxn], rs[maxn]; 9 int stac[maxn], top; 10 inline int read() 11 { 12 int x = 0, f = 1; 13 char c = getchar(); 14 for (; !isdigit(c); c = getchar()) 15 if (c == '-') 16 f = -1; 17 for (; isdigit(c); c = getchar()) 18 x = x * 10 + c - '0'; 19 return x * f; 20 } 21 int build(int x) 22 { 23 stac[++top] = x; 24 while(top) 25 { 26 x = stac[top]; 27 if(!vis[x]) 28 { 29 vis[x]=1; 30 if(ls[x]) 31 { 32 stac[++top] = ls[x]; 33 } 34 } 35 else 36 { 37 a[++cnt] = val[x]; 38 top--; 39 if(rs[x]) 40 { 41 stac[++top] = rs[x]; 42 } 43 } 44 } 45 } 46 47 int main() 48 { 49 n = read(); 50 for (int i = 1; i <= n;i++) 51 { 52 val[i]=read(); 53 } 54 for (int i = 2;i<=n;i++) 55 { 56 int flag=read(), t=read(); 57 if(!t) 58 { 59 ls[flag] = i; 60 } 61 else 62 { 63 rs[flag] = i; 64 } 65 } 66 build(1); 67 for (int i = 1; i <= n;i++) 68 { 69 a[i] -= i; 70 g[i]=2e9; 71 } 72 for (int i = 1; i <= n;i++) 73 { 74 int k = upper_bound(g + 1, g + 1 + n, a[i]) - g; 75 ans = max(ans, k); 76 g[k] = a[i]; 77 } 78 cout << n - ans << endl; 79 return 0; 80 }