D
题意
一团火在 (0,0)产生烟雾,一个人在(R,C),现在有 T
个时间点,每个时间点会把产生的烟向一个方向吹去,且(0,0)会不断产生烟,求烟和人重合的时间点。
题解
模拟烟的路径是不现实的,考虑到相对运动,烟动和人动是等价的,因此可以改变人的位置,还需要考虑新生成的烟:在原来的参考系中(0,0)和(R,C)是相对静止的,因此新的参考系新生成的烟和人一起动。
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 #include <bits/stdc++.h> using namespace std;using i64 = long long ;using u64 = unsigned long long ;using u32 = unsigned ;using u128 = unsigned __int128;void solve () { int n, r, c; cin >> n >> r >> c; set<array<int , 2>> smoke; int x = 0 , y = 0 ; smoke.insert ({x, y}); string s; cin >> s; for (int i = 0 ; i < n; i++) { switch (s[i]) { case 'N' : x++; break ; case 'W' : y++; break ; case 'S' : x--; break ; case 'E' : y--; break ; } smoke.insert ({x, y}); cout << smoke.count ({r + x, c + y}); } } signed main () { ios::sync_with_stdio (false ); cin.tie (0 ); int t = 1 ; while (t--) { solve (); } return 0 ; }
E
题意
交互题。
给定一棵树,和对手轮流加边,要求形成的环大小不能是奇数,决定先后手和加边方式让自己胜利。
题解
赛时找了一下性质:如果能加边,那么两节点深度之和必须是奇数,且不能相邻(认为根节点深度为1),因为这样的话两节点之间间隔的节点数目一定是偶数个。那么
\(O(n^2)\)
遍历所有节点,记录可以加的边即可,如果为奇数条就先手,否则后手。
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 #include <bits/stdc++.h> using namespace std;using i64 = long long ;using u64 = unsigned long long ;using u32 = unsigned ;using u128 = unsigned __int128;void solve () { int n; cin >> n; vector<vector<int >> e (n + 1 ); vector<int > dep (n + 1 ) , p (n + 1 ) ; for (int i = 1 ; i < n; i++) { int x, y; cin >> x >> y; e[x].push_back (y); e[y].push_back (x); } set<pair<int , int >> st; auto dfs = [&](auto dfs, int x, int fa) -> void { p[x] = fa; for (auto y : e[x]) { if (y == fa) { continue ; } dep[y] = dep[x] + 1 ; dfs (dfs, y, x); } }; dep[1 ] = 1 ; dfs (dfs, 1 , 0 ); for (int i = 1 ; i <= n; i++) { for (int j = i + 1 ; j <= n; j++) { if (p[i] == j || p[j] == i) { continue ; } int d = dep[i] + dep[j] - 1 ; if (d % 2 == 0 && d >= 4 ) { st.insert ({i, j}); } } } if (st.size () % 2 == 1 ) { cout << "First" << endl; cout << (*st.begin ()).first << " " << (*st.begin ()).second << endl; st.erase (st.begin ()); while (!st.empty ()) { int x, y; cin >> x >> y; if (x == -1 && y == -1 ) { return ; } if (x > y) { swap (x, y); } st.erase (st.find ({x, y})); cout << (*st.begin ()).first << " " << (*st.begin ()).second << endl; st.erase (st.begin ()); } } else { cout << "Second" << endl; while (!st.empty ()) { int x, y; cin >> x >> y; if (x == -1 && y == -1 ) { return ; } if (x > y) { swap (x, y); } st.erase (st.find ({x, y})); cout << (*st.begin ()).first << " " << (*st.begin ()).second << endl; st.erase (st.begin ()); } } } signed main () { ios::sync_with_stdio (false ); cin.tie (0 ); int t = 1 ; while (t--) { solve (); } return 0 ; }
后来补题时发现可以用二分图的思想来做:因为不能形成奇环,那么最后形成的一定是一张二分图,对原树进行染色后,设两集合大小分别为
\(A,B\) ,那么能加的边数就是 \(AB -
(n-1)\) ,根据奇偶性判断先后手然后枚举边交互即可。
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 #include <bits/stdc++.h> using namespace std;using i64 = long long ;using u64 = unsigned long long ;using u32 = unsigned ;using u128 = unsigned __int128;void solve () { int n; cin >> n; vector<vector<int >> e (n + 1 ); vector<vector<bool >> g (n + 1 , vector <bool >(n + 1 )); for (int i = 1 ; i < n; i++) { int x, y; cin >> x >> y; e[x].push_back (y); e[y].push_back (x); g[x][y] = g[y][x] = true ; } queue<int > q; vector<int > f (n + 1 , -1 ) ; bool is_Bipartite = true ; for (int i = 1 ; i <= n; i++) { if (f[i] == -1 ) { q.push (i); f[i] = 0 ; while (!q.empty ()) { int x = q.front (); q.pop (); for (auto y : e[x]) { if (f[y] == -1 ) { f[y] = f[x] ^ 1 ; q.push (y); } else if (f[x] == f[y]) { is_Bipartite = false ; break ; } } } } if (!is_Bipartite) { break ; } } int cnt0 = 0 , cnt1 = 0 ; for (int i = 1 ; i <= n; i++) { cnt0 += (f[i] == 0 ); cnt1 += (f[i] == 1 ); } int cur = 1 ; if (((cnt0 * cnt1) - (n - 1 )) % 2 == 0 ) { cout << "Second" << endl; } else { cout << "First" << endl; cur = 0 ; } int x = 2 , y = 1 ; while (true ) { if (cur == 0 ) { while (g[x][y] || f[x] == f[y]) { y++; if (y == x) { x++; y = 1 ; } } g[x][y] = g[y][x] = true ; cout << y << " " << x << endl; } else { int x, y; cin >> x >> y; if (x == -1 ) { break ; } g[x][y] = g[y][x] = true ; } cur ^= 1 ; } } signed main () { ios::sync_with_stdio (false ); cin.tie (0 ); int t = 1 ; while (t--) { solve (); } return 0 ; }
F
题意
给定一个字符串,找到一个字符串,这个字符串的前缀是原串,且是一个回文串。
题解
观察一下可以得出,既然新串要求是回文串,那么我们只需要找原串的后缀中最长的回文串,然后把剩下的部分反转一下输出到后面去即可。
可以使用 Manacher
算法来处理每个位置的最长回文串半径,之后我们枚举一个位置 \(l\) ,满足这个位置开始的后缀是回文串。由于
Manacher 算法得到的数组 \(r\)
对应原串的长度就是 \(r -
1\) ,因此只需要从中心开始,判断 \(r\) 值能否到达末尾即可。
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 #include <bits/stdc++.h> using namespace std;using i64 = long long ;using u64 = unsigned long long ;using u32 = unsigned ;using u128 = unsigned __int128;vector<int > manacher (string s) { string t = "#" ; for (auto c : s) { t += c; t += "#" ; } int n = t.size (); vector<int > r (n) ; for (int i = 0 , j = 0 ; i < n; i++) { if (2 * j - i >= 0 && j + r[j] > i) { r[i] = min (r[2 * j - i], j + r[j] - i); } while (i - r[i] >= 0 && i + r[i] < n && t[i - r[i]] == t[i + r[i]]) { r[i]++; } if (i + r[i] > j + r[j]) { j = i; } } return r; } void solve () { string s; cin >> s; auto r = manacher (s); int n = s.size (); int l = 0 ; while (r[l + n] < n - l + 1 ) { l++; } string t = s.substr (0 , l); reverse (t.begin (), t.end ()); cout << s + t << "\n" ; auto ans = s; } signed main () { ios::sync_with_stdio (false ); cin.tie (0 ); int t = 1 ; while (t--) { solve (); } return 0 ; }