everyday two problems / 3.1

T1.string

题意:

给定由小写字母组成的长度为 n 的字符串

将其所有 n * (n + 1) / 2 个子串按字典序排序

输出第 k 个子串

k > (n * (n + 1) / 2) 就输出"No such line."

1 <= n, k <= 1e5

解法:

解法很多(我不会写自动机啊

我用的解法应该算是比较简单

一位一位地去计数来确定当前位的字母

比如一开始O(n)扫描出以a开头的子串有3个

以b开头的子串有4个,而我要输出排名第5的子串

所以这一位一定是'b',后面每一位字母的确定同理

当前位是空格的时候标志着结果输出结束

代码比较简单,可以参考

但是我们要说明一下效率

一开始脑残了误以为是O(26n)

但是其实每次while中运行次数与之前已经输出的子串在原字符串中出现的次数有关

那么我们可以考虑最坏情况,10w个'a'

这时候如果要求输出排名最靠后的子串的话效率已经O(n^2)

还好我们题目有限制 k <= 1e5

所以这种做法的最坏效率其实比较玄学(但是过了就好了233

 1 #include <cstdio>
 2 #include <vector>
 3 #include <cstring>
 4 
 5 using namespace std;
 6 
 7 typedef long long ll;
 8 
 9 vector <int> p[27], pp;
10 
11 char str[100010];
12 
13 ll cnt[27];
14 
15 int n, m;
16 
17 int main() {
18     scanf("%s %d", str, &m);
19     n = strlen(str);
20     if(1ll * n * (n + 1) / 2 < m) {
21         puts("No such line.");
22         return 0;
23     }
24     for(int j, i = 0;i < n;i ++) {
25         j = str[i] - 96;
26         cnt[j] += n - i;
27         p[j].push_back(i);
28     }
29     while(1) {
30         int pos;
31         for(int i = 0;i <= 26;i ++) {
32             if(cnt[i] >= m) {
33                 pos = i;
34                 if(!pos) return 0;
35                 putchar(96 + i);
36                 break;
37             }
38             else m -= cnt[i];
39         }
40         for(int i = 1;i <= 26;i ++) {
41             cnt[i] = 0;
42             if(i != pos) p[i].clear();
43         }
44         cnt[0] = p[pos].size();
45         for(int k, j, i = 0;i < p[pos].size();i ++) {
46             j = p[pos][i] + 1;
47             if(j >= n) continue;
48             k = str[j] - 96;
49             cnt[k] += n - j;
50             if(k == pos) pp.push_back(j);
51             else p[k].push_back(j);
52         }
53         p[pos].clear();
54         for(int i = 0;i < pp.size();i ++)
55             p[pos].push_back(pp[i]);
56         pp.clear();
57     }
58 }
View Code

T2.Levko and Array

 

题意:

给定数组a[2000],-1e9 <= a[i] <= 1e9

最多允许修改 k(k <= n) 个元素

求 max(abs(a[i] - a[i + 1])) 的最小值 

解法:

这题是看别人题解做的

猜测正解效率可能n^2

但并没有什么策略能直接由条件得到答案

满足单调,考虑二分,可能效率n^2logn,可以接受

然后这个judge函数就比较神了

考虑dp,f[i]表示先只考虑前 i 个且第 i 个不动的情况下

最少修改多少个能够让前 i 个的 max(abs(a[i] - a[i + 1])) <= mid

转移方程 f[i] = min(f[i], f[j] + i - j - 1)

转移条件是abs(f[i] - f[j]) <= (i - j) * mid

即,保证i j 都不变,(i, j)都改变

要能够使[i, j]任意相邻两个之差 <= mid才能转移

上面我们提到f[i]的意义,只考虑了前 i 个

但我们考虑最优解一定有最后连续 t 个都是要被修改的(0 <= t < n)

所以我们计算完所有 f[i] 之后

在从 1 到 n 扫一遍 f[i] + n - i

是一定能够包含最优解的

当然f[i] + n - i <= k就可以返回true了

参考的题解

 1 #include <cstdio>
 2 
 3 typedef long long ll; 
 4 
 5 int n, k, a[2010], f[2010];
 6 
 7 int min(int x, int y) {
 8     return x < y ? x : y;
 9 }
10 
11 int dis(int x, int y) {
12     if(x < y) return y - x;
13     return x - y;
14 }
15 
16 bool judge(ll d) {
17     f[1] = 0;
18     for(int i = 2;i <= n;i ++) {
19         f[i] = 66666;
20         for(int j = i- 1;j;j --) 
21             if(dis(a[i], a[j]) <= d * (i - j))
22                 f[i] = min(f[i], f[j] + i - j - 1);
23         if(i <= k + 1) f[i] = min(f[i], i - 1);
24     }
25     for(int i = 1;i <= n;i ++)
26         if(f[i] + n - i <= k)
27             return 1;
28     return 0;
29 }
30 
31 int main() {
32     scanf("%d %d", &n, &k);
33     for(int i = 1;i <= n;i ++) 
34         scanf("%d", &a[i]);
35     ll l = 0, r = 2e9, mid;
36     while(l <= r) {
37         mid = (l + r) >> 1;
38         if(judge(mid)) r = mid - 1;
39         else l = mid + 1;
40     }
41     printf("%I64d", l);
42     return 0;
43 }
View Code
原文地址:https://www.cnblogs.com/ytytzzz/p/6498597.html