https://codeforces.com/contest/1324
A. Yet Another Tetris Problem
n列的俄罗斯方块画面,现在第\(i\)列已经有\(a_i\)块方块了,问能不能用若干个一列两行的方块把所有方块Sakuzyo。
判所有数字奇偶性是否相同就行,有手就能过
#include <bits/stdc++.h> #define LL long long #define pb push_back #define eb emplace_back #define pii pair<int,int> #define pll pair<LL,LL> using namespace std; int T, n, a[108]; bool gao() { for (int i = 1; i <= n; ++i) { if (a[i] % 2 != a[1] % 2) return false; } return true; } int main() { cin >> T; while (T--) { cin >> n; for (int i = 1; i <= n; ++i) cin >> a[i]; cout << (gao()? "YES":"NO") << endl; } }
B. Yet Another 有手就行Palindrome Problem
给一个长为n(\(\leq5000\))的数组,问是否存在一个长度至少为3的子序列是回文的。回文的定义是把序列reverse,序列不变,如[10,20,10]就是回文的。
考虑奇数长度的回文序列,删除其首尾元素仍然回文;再考虑偶数长度的回文序列,删除最中间那一对的某个元素,变成奇数长度的回文序列;因此原题等价于判断是否存在一个长度为3的子序列。for两遍就行。
#include <bits/stdc++.h> #define LL long long #define pb push_back #define eb emplace_back #define pii pair<int,int> #define pll pair<LL,LL> using namespace std; int T, n, a[5008], b[5008]; bool gao() { for (int i = 1; i <= n; ++i) { for (int j = i + 1; j <= n; ++j) { if (b[a[j]]) return true; } b[a[i]] = 1; } return false; } int main() { cin >> T; while (T--) { memset(b, 0, sizeof(b)); cin >> n; for (int i = 1; i <= n; ++i) cin >> a[i]; cout << (gao()? "YES":"NO") << endl; } }
C. Frog Jumps
给你一个由L和R组成的字符串\(s=Rs_1s_2...s_n(n\leq2\cdot10^5)\)。有只🐸站在最左边那个R那一格上,要跳到\(s_{n+1}\)的位置。规定站在L上只能往左跳,站在R上只能往右跳,一次最多跳k格。问k最小能到多少使得🐸至少有一种方案能跳过去。
很显然🐸应该避免跳到L上。那么原问题等价于求字符串里最长的全L子串的长度。
假设最长的全L字串的长度为\(k-1\),那么🐸必须要\(k\)的最大步长才能跳过这一段L,否则必定跳到其中某一个L上而只能往回跳。而很显然\(k\)的步长足够了,因为此时🐸在R上必定能花不多于k步往后跳到另一个R上。
#include <bits/stdc++.h> #define LL long long #define pb push_back #define eb emplace_back #define pii pair<int,int> #define pll pair<LL,LL> using namespace std; int T; char s[200008]; int main() { scanf("%d", &T); while (T--) { scanf("%s", s + 1); int n = strlen(s + 1); int c = 0, ans = 0; for (int i = 1; i <= n + 1; ++i) { if (s[i] == 'L') { c++; } else { ans = max(ans, c); c = 0; } } cout << ans + 1 << endl; } }
D. Pair of Topics
给两个长为\(n(\leq2\cdot 10^5)\)的数组\([a_1,a_2,...,a_n]\)和\([b_1,b_2,...,b_n]\)(\(1\leq a_i,b_i\leq 10^9\)),问存在多少对\(i,j(1\leq i<j\leq n)\)使得\(a_i+a_j>b_i+b_j\)。
先变个形,\(a_i-b_i>-(a_j-b_j)\)。设\(c_i=a_i-b_i\)那就是\(c_i+c_j>0\)。那就相当于给了一个数组问有多少对数的和大于0。两个正数的和必定大于0,因此关键就是统计有多少对一正一负的数。
不妨把所有正数提到一个数组\(P\)里,把0和负数提到另一个数组\(N\)里。把\(P\)升序排序。从前往后遍历\(P\)中的每一元素\(p_i\)。维护\(N\)的一个子集使得\(N\)中任意元素\(x\)都满足\(p_i+x>0\)。抽象一点的话,我们记\(N_i=\{x|x\in N\ and\ x+p_i>0\}\)。因为\(p_i<p_{i+1}\),所以若\(x+p_i>0\),那么\(x+p_{i+1}>0\)一定成立,所以有\(N_i\subseteq N_{i+1}\)。简单来说,就是已经满足条件了的负数在\(i\)前进时肯定满足条件。
那么只要把\(N\)降序排序,在N上二分查找就行。\(O(n\ logn)\)。
也可以排序之后维护\(N\)的一个指针\(j\),在枚举\(i\)时把\(j\)适当向后移使得\(p_i+n_j>0,p_i+n_{j+1}\leq 0\)就行了。总的时间复杂度还是\(O(n\ logn)\)。很经典的双指针做法。
(我没用这个做法所以不贴代码了
当然如果脑抽了,想不到这个办法,还可以上线段树:枚举\(j\)求有多少个\(i<j\)使得\(c_i>-c_j\)。那就直接区间查询,单点更新就完事了。当然值域太大,需要上一个离散化。
#include <bits/stdc++.h> #define LL long long #define pb push_back #define eb emplace_back #define pii pair<int,int> #define pll pair<LL,LL> using namespace std; int n, a[200008], b[200008], tre[(200004 << 2)]; LL ans = 0; map<int, int> mp; void pushup(int rt) { tre[rt] = tre[rt << 1] + tre[rt << 1 | 1]; } void update(int rt, int l, int r, int loc) { if (l == r) {tre[rt]++;return;} int mid = (l + r) >> 1; if (loc <= mid) update(rt << 1, l, mid, loc); else update(rt << 1 | 1, mid + 1, r, loc); pushup(rt); } int query(int rt, int l, int r, int L, int R) { if (L <= l && r <= R) return tre[rt]; int mid = (l + r) >> 1; int ret = 0; if (L <= mid) ret += query(rt << 1, l, mid, L, R); if (mid + 1 <= R) ret += query(rt << 1 | 1, mid + 1, r, L, R); return ret; } int main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) { scanf("%d", &a[i]); } for (int i = 1; i <= n; ++i) { int x; scanf("%d", &x); a[i] -= x; } for (int i = 1; i <= n; ++i) { b[i] = a[i]; } sort(b + 1, b + n + 1); int c = 0; for (int i = 1; i <= n; ++i) { if (!mp[b[i]]) { mp[b[i]] = ++c; } } for (int i = 1; i <= n; ++i) { int* v = upper_bound(b + 1, b + n + 1, -a[i]); if (v <= b + n) { ans += query(1, 1, n, mp[*v], n); } update(1, 1, n, mp[a[i]]); } cout << ans << endl; }
E. Sleeping Schedule
某个星球上一天有\(h\)小时\((0,1,2,...,h-1)(h\leq 2000)\),现在有个人在某一天的0点整睡醒了。他现在要睡\(n(\leq 2000)\)次觉,其中第\(i\)次睡觉必定会在他醒来的\(a_i(1\leq a_i\leq h-1)\)小时之后发生。他每次都正好睡\(h\)小时醒来。
给定一对\(l,r(0\leq l<r\leq h-1)\),他觉得如果一次睡觉在\(l,l+1,...,r\)这几个时刻中的某一个时刻开始,那这次睡觉比较好。
现在他成为了大脑升级人,可以用大脑控制自己的清醒时间了,使得自己正好提早一小时睡觉(即选择一些数把它们减去1)。问他最多能得到多少次好的睡觉。
动态规划。\(f(i,j)\)表示睡完了前\(i-1\)次,在时刻\(j\)醒来,能得到的好睡觉的次数最大值。
那么两种状态转移:\(f(i,j)\to f(i+1,(j+a_i)\%h)\)和\(f(i,j)\to f(i+1,(j+a_i-1)\%h)\)。
答案就是\(\mathop{max}\limits_{j=0}^{h-1}{f(n+1,j)}\)。
非法状态注意处理一下就行。
#include <bits/stdc++.h> #define LL long long #define pb push_back #define eb emplace_back #define pii pair<int,int> #define pll pair<LL,LL> using namespace std; int n, h, l, r, dp[2008][2008], a[2008]; int gao(int x) { return l <= x && x <= r; } int main() { scanf("%d %d %d %d", &n, &h, &l, &r); for (int i = 0; i < n; ++i) { scanf("%d", &a[i]); } memset(dp, -1, sizeof(dp)); dp[0][0] = 0; for (int i = 0; i < n; ++i) { for (int j = 0; j < h; ++j) { if (dp[i][j] < 0) continue; //printf("dp[%d][%d] = %d\n", i, j, dp[i][j]); dp[i + 1][(j + a[i] - 1) % h] = max(dp[i + 1][(j + a[i] - 1) % h], dp[i][j] + gao((j + a[i] - 1) % h)); dp[i + 1][(j + a[i]) % h] = max(dp[i + 1][(j + a[i]) % h], dp[i][j] + gao((j + a[i]) % h)); } } int ans = 0; for (int j = 0; j < h; ++j) ans = max(ans, dp[n][j]); cout << ans << endl; }
F. Maximum White Subtree
给一棵无根树,一些点是黑的,另外的点是白的。定义一棵树的价值是它的白点数量减去黑点数量。现在对树上的每个点\(v\),询问所有包含点\(v\)的子树的最大价值是多少(换句话说,就是切掉一些子树,不能切掉\(v\),使得价值最大)。
点数\(n\leq 2\cdot10^5\)。
先考虑单个询问\(v\)的问题,不妨考虑\(v=1\):
把这棵树看成以\(1\)为根的有根树,设\(f(u)\)为以\(u\)为根的子树能达到的最大价值(必须包含\(u\)),那么很显然它满足一个子结构性:对\(u\)的每个儿子\(v\),要么把\(v\)这棵子树切掉,那么贡献是0,要么把\(v\)这棵子树留下,那么贡献是\(f(v)\)。子树之间的选择不相互干扰。那就有了\(f(u)=\sum\limits_{v\in son_u}max(0,f(v))\)。这就是个树形dp。
现在算出了以\(1\)为根的一系列\(f(u)\)值。设\(g(u)\)为点\(u\)的答案,那么就有了\(g(1)=f(1)\)。但是如果对每个点都去dp一次的话,复杂度是\(O(n^2)\)的,自然无法接受。
还是在以\(1\)为根的有根树上考虑:
假如说我知道了\(u\)这个点的答案\(g(u)\),那么我考虑它的一个儿子\(v\),假如把\(v\)这颗子树切出来,\(g(u)\)就会变成\(g(u)-max(0,f(v))\),这就是包含\(u\)但不包含\(v\)的最大价值。现在算\(g(v)\),就相当于要以\(v\)为根去dp了,那么事实上\(g(v)\)包含两部分:一是\(f(v)\),因为\(v\)这个点必须要选;二是原来是它的父亲,现在是它的儿子了(大雾???)的\(u\)的贡献,也就是包含\(u\)但不包含\(v\)的最大价值。
如图,切开\(u,v\)这条边,分开考虑。
因此,\(g(v)=g(u)-max(0,f(v))+f(v)\)。
那么我就能以\(O(1)\)的时间把答案从根转给儿子。
#include <bits/stdc++.h> #define LL long long #define pb push_back #define eb emplace_back #define pii pair<int,int> #define pll pair<LL,LL> using namespace std; int n, a[200008], dp[200008], ans[200008]; vector<int> mp[200008]; void dfs(int rot, int pre) { for (auto nxt: mp[rot]) { if (nxt == pre) continue; dfs(nxt, rot); dp[rot] += max(0, dp[nxt]); } dp[rot] += (a[rot]?1:-1); } void dfs2(int rot, int pre) { for (auto nxt: mp[rot]) { if (nxt == pre) continue; int x = ans[rot] - max(0, dp[nxt]); ans[nxt] = dp[nxt] + max(0, x); dfs2(nxt, rot); } } int main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%d", &a[i]); for (int i = 1; i < n; ++i) { int x, y; scanf("%d %d", &x, &y); mp[x].pb(y); mp[y].pb(x); } dfs(1, 0); ans[1] = dp[1]; dfs2(1, 0); for (int i = 1; i <= n; ++i) printf("%d ", ans[i]); printf("\n"); }
后记
这好像还是我第一次在赛中ak了,之前还有两三次都是差一题ak,赛后就过了。
不过学弟也ak了,而且他还在1:50的时候截了个ak图出来,而我是在1:51才交的最后一题......
那就先祝他fst吧(?
(ak一个div3有什么好开心的,这不是应该的吗(
来源:https://www.cnblogs.com/zhugezy/p/12484182.html