终于不挂分了!(虽然 T4 结论猜错了)
T1 sequence 思路分析 记 \(f_i\) 表示以 \(a_i\) 为结尾的严格连续递增子序列长度,\(g_i\) 表示以 \(a_i\) 为开头的严格连续递增子序列长度,那么容易发现求的是 \(\displaystyle \max_{j<i,a_j<a_i}(f_j+g_i)\) 。
考虑枚举 \(i\) ,则我们要找到满足 \(j<i\) 且 \(a_j<a_i\) 的 \(j\) 中最大的 \(f_j\) 。将问题转化到值域上的 RMQ,想象一个数轴,将 \(f_j\) 插入到数轴上 \(a_j\) 对应的位置,则要求的就是数轴上 \([1,a_i-1]\) 的最大值。
由于值域有 \(10^9\) ,不好处理,所以先离散化。求的是前缀最大值,最方便的做法是用树状数组维护,则 \(O(n \log n)\) 解决此题。
AC 代码 code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #include <bits/stdc++.h> using namespace std;#define FILE_IO namespace io {...};using namespace io;const int maxn = 2e5 + 5 ;int t, n, m;int a[maxn], b[maxn], pre[maxn], nxt[maxn];int c[maxn];void update (int pos, int x) { while (pos <= m) { c[pos] = max (c[pos], x); pos += (pos & (-pos)); } } int ask (int pos) { int res = 0 ; while (pos) { res = max (res, c[pos]); pos -= (pos & (-pos)); } return res; } int main () { read (t); while (t--) { read (n); memset (c, 0 , sizeof c); int ans = 0 ; a[0 ] = 0x3f3f3f3f , a[n + 1 ] = 0xcfcfcfcf ; pre[0 ] = pre[n + 1 ] = nxt[0 ] = nxt[n + 1 ] = 0 ; for (int i = 1 ; i <= n; i++) { read (a[i]); b[i] = a[i]; if (a[i] > a[i - 1 ]) pre[i] = pre[i - 1 ] + 1 ; else pre[i] = 1 ; } for (int i = n; i >= 1 ; i--) { if (a[i] < a[i + 1 ]) nxt[i] = nxt[i + 1 ] + 1 ; else nxt[i] = 1 ; } sort (b + 1 , b + n + 1 ); m = unique (b + 1 , b + n + 1 ) - b - 1 ; for (int i = 1 ; i <= n; i++) { a[i] = lower_bound (b + 1 , b + m + 1 , a[i]) - b; } for (int i = 1 ; i <= n; i++) { ans = max (ans, ask (a[i] - 1 ) + nxt[i]); update (a[i], pre[i]); } write (ans); } return 0 ; }
T2 tower 思路分析 显然答案具有单调性,如果 \(t\) 分钟之内能消灭,大于 \(t\) 分钟必然能消灭。
考虑二分转化为判定。在 \(t\) 分钟的时间限制内,每个防御塔都可以打出 \(\lfloor\frac{t+t_2}{t_1+t_2}\rfloor\) 颗导弹。我们把这个数记为 \(p\) ,则总共有 \(np\) 颗导弹。
观察一下就会发现,一个导弹消灭一个敌人,这是一个匹配问题,而且敌人内部和导弹内部不会匹配,想到建图跑二分图最大匹配。
对于每一颗导弹和每个敌人都建一个点。每个导弹的发射时间都可以计算出来,记作 \(st_i\) ,则将这颗导弹在剩下的 \(t-st_i\) 分钟之内能够到达的所有敌人都和这颗导弹连边,最后跑匈牙利 就可以完成判定。
AC 代码 code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 #include <bits/stdc++.h> using namespace std;namespace io {...};using namespace io;const int maxn = 55 ;int n, m;double t1, t2, v;int tx[maxn], ty[maxn], ex[maxn], ey[maxn];double titi[maxn][maxn];int pf (int x) { return x * x; } double tim (int tower, int enemy) { return sqrt (double (pf (tx[tower] - ex[enemy]) + pf (ty[tower] - ey[enemy]))) / v; } vector<int > g[maxn]; int cnt[maxn];bool book[maxn * maxn];int book2[maxn * maxn];bool dfs (int u) { for (int i : g[u]) { if (!book[i]) { book[i] = true ; if (!book2[i] || dfs (book2[i])) { book2[i] = u; return true ; } } } return false ; } bool check (double t) { int p = (t + t2) / (t1 + t2); p = min (p, m); for (int i = 1 ; i <= m; i++) { for (int j = 1 ; j <= n; j++) { g[i].clear (); for (int id = 1 ; id <= p; id++) { if (titi[j][i] - t2 + id * (t1 + t2) <= t) g[i].emplace_back ((j - 1 ) * p + id); } } } memset (book2, 0 , sizeof book2); for (int i = 1 ; i <= m; i++) { memset (book, 0 , sizeof book); if (!dfs (i)) return false ; } return true ; } vector<double > ts; int main () { read (n); read (m); int tmp; read (tmp); t1 = double (tmp) / 60 ; read (tmp); t2 = tmp; read (tmp); v = tmp; for (int i = 1 ; i <= m; i++) { read (ex[i]); read (ey[i]); } for (int i = 1 ; i <= n; i++) { read (tx[i]); read (ty[i]); } for (int i = 1 ; i <= m; i++) { for (int j = 1 ; j <= n; j++) { titi[j][i] = tim (j, i); } } for (int id = 1 ; id <= m; id++) { for (int i = 1 ; i <= m; i++) { for (int j = 1 ; j <= n; j++) { ts.emplace_back (titi[j][i] - t2 + id * (t1 + t2)); } } } sort (ts.begin (), ts.end ()); int l = 0 , r = ts.size () - 1 , mid; while (l < r) { mid = (l + r) >> 1 ; if (check (ts[mid])) r = mid; else l = mid + 1 ; } printf ("%.6lf\n" , ts[l]); return 0 ; }
T3 photo 思路分析 可以看出符合 \(h_i < h_{i+1} + 2\) 的排列有一个特点:一定是将一个顺序序列分成多个部分,并将每个部分都逆转。比如 \([1,2,3,4,5]\) 的 1 到 3 和 4 到 5分别反转,就是 \([3,2,1,5,4]\) 。
注意:接下来所有的下标表示的都是值域,并不是序列的坐标,而是序列中的值!!!
如果我们能求出将原序列的 \([l,r]\) 部分交换成全部反转所需的步骤(记作 \(g_{l,r}\) ),就可以用 dp 解决此题。定义 \(f_i\) 表示只考虑 \([1,i]\) 的值域 区间所需的最小步骤,那么可以通过枚举最后一段连续下降序列的长度来转移,即:\(f_i\leftarrow\min_{j=1}^{i - 1}{(f_{j}+g_{j+1,i})}\) 。
剩下的问题就是考虑怎么快速求 \(g\) 。在目标序列中,\([l,r]\) 如果是一段连续下降区间,那么 \([l,r]\) 内部会产生全部 \(\frac{(r - l + 1) \times (r - l)}{2}\) 个逆序对,但是 \([l,r]\) 与 \([1,l-1],[r+1,n]\) 之间不会产生任何逆序对。我们可以枚举出原序列的 \([l,r]\) 内部还差多少个逆序对没产生(有多少个“顺序对”),以及 \([l,r]\) 与外部产生了几个多余的逆序对,则 \(g_{l,r}\) 就是内部“顺序对”与外部逆序对之和。
而逆序对和顺序对本身是很好统计的。以逆序对为例:用 \(b_{i,j}\) 表示在原序列中 \(i\) 和 \(j\) 两个数是否产生了逆序对,那么对 \(b\) 做二维前缀和得到 \(s\) ,\(s_{i,j}\) 就表示第一个数小于等于 \(i\) ,第二个数小于等于 \(j\) 的逆序对个数。逆序对对于 \(g_{l,r}\) 的贡献(即 \([l,r]\) 与外部的逆序对)就是 \(s_{r,l-1}-s_{l-1,l-1}\) 。
AC 代码 code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <bits/stdc++.h> using namespace std;#define FILE_IO namespace io {...};using namespace io;const int maxn = 5e3 + 5 ;int n;int h[maxn];int rev[maxn][maxn], drev[maxn][maxn], dp[maxn];int calc (int l, int r) { int res = 0 ; res += (drev[r][r] - drev[l - 1 ][r] - drev[r][l - 1 ] + drev[l - 1 ][l - 1 ]); res += rev[r][l - 1 ] - rev[l - 1 ][l - 1 ]; return res; } int main () { read (n); for (int i = 1 ; i <= n; i++) { read (h[i]); } for (int i = 1 ; i <= n; i++) { for (int j = i + 1 ; j <= n; j++) { if (h[i] > h[j]) rev[h[i]][h[j]]++; else drev[h[i]][h[j]]++; } } for (int i = 1 ; i <= n; i++) { for (int j = 1 ; j <= n; j++) { drev[i][j] += drev[i][j - 1 ] + drev[i - 1 ][j] - drev[i - 1 ][j - 1 ]; rev[i][j] += rev[i][j - 1 ] + rev[i - 1 ][j] - rev[i - 1 ][j - 1 ]; } } memset (dp, 0x3f , sizeof dp); dp[0 ] = 0 ; for (int i = 1 ; i <= n; i++) { for (int j = i; j >= 1 ; j--) { dp[i] = min (dp[i], dp[j - 1 ] + calc (j, i)); } } write (dp[n]); return 0 ; }
T4 toilet 思路分析 为了 \(n\) 分钟之内结束,两个厕所一刻都不能停。由于男生不能进女厕,所以一个男生必须要和一个女生一起才能上厕所(奇怪啊)。如果把男生视为 \(1\) ,女生视为 \(-1\) ,那么对于任意位置,后缀和必须要小于等于 \(1\) 才能保证成功。(后缀和为 \(1\) 时多出来的那个男生可以跟着他之前的女生走)。
如果出现了后缀和大于 \(1\) 怎么办呢?就需要把一些男生放到前面去。如果累计的男生个数有 \(x\) 个,则至少要放 \(x-1\) 个到女生前面,则答案应该对 \(x-1\) 取 \(\max\) 。
这样的话,考虑从后往前扫描字符串,用一个变量来存储后缀和,当后缀和大于 \(1\) 时,更新一下答案。
然后考虑怎么处理重复字符串。容易想到,重复的字符串对于后缀和的影响是相同的,并且无论影响是正是负,对答案有影响的只有可能是第一个串和所有串连在一起。所以维护一个字符串产生的后缀和变化量 \(delta\) 以及变化过程中的最大变化量 \(maxdelta\) ,如果一共重复 \(t\) 次,并且在这个字符串之前的后缀和是 \(cnt\) ,那么在这些重复的字符串中,最大后缀和是 \(\max(cnt+maxdelta, cnt+maxdelta+delta\times(t-1))\) ,而对总后缀和的影响是 \(cnt \leftarrow cnt + delta\times t\) 。
最后看一下无解:如果总后缀和最终大于 \(0\) (\(1\) 也不行,如果是 \(1\) 第一个男生无法上厕所),说明男生过多了,则输出 \(-1\) 。否则输出过程中的最大后缀和减一。
AC 代码 code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <bits/stdc++.h> using namespace std;namespace io {...};using namespace io;const int maxm = 1e5 + 5 ;long long n, ans;int m;string s[maxm]; long long t[maxm];int main () { read (n); read (m); for (int i = 1 ; i <= m; i++) { cin >> s[i]; read (t[i]); reverse (s[i].begin (), s[i].end ()); } long long cnt = 0 ; for (int i = m; i >= 1 ; i--) { long long delta = 0 , mxc = 0 ; for (int j = 0 ; j < s[i].size (); j++) { if (s[i][j] == 'F' ) delta--; else delta++; mxc = max (mxc, delta); } ans = max (ans, mxc + cnt); ans = max (ans, mxc + cnt + delta * (t[i] - 1 )); cnt += delta * t[i]; } if (cnt > 0 ) write (-1 ); else if (ans > 1 ) write (ans - 1 ); else write (0 ); return 0 ; }