【比赛链接】
【题解链接】
【C】2D Plane 2N Points
【思路要点】
- 按照纵坐标从小到大的顺序来考虑每一个点。
- 若当前点为红点,那么将其横坐标加入当前可用的红点的横坐标集合。
- 若当前点为蓝点,令其与当前可用的红点集合中横坐标恰小于当前点横坐标的红点匹配,并将该红点标记为不可用,若不存在这样的红点,则放弃该蓝点的匹配。
- 用std::set维护当前可用的红点的横坐标集合,时间复杂度\(O(NLogN)\)。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 100005; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } set <int> st; bool type[MAXN]; int pos[MAXN]; int main() { int n; read(n); for (int i = 1; i <= n; i++) { int x, y; read(x), read(y); type[x] = true, pos[x] = y; } for (int i = 1; i <= n; i++) { int x, y; read(x), read(y); type[x] = false, pos[x] = y; } int ans = 0; for (int i = 0; i < 2 * n; i++) { if (type[i]) st.insert(pos[i]); else { set <int> :: iterator tmp = st.lower_bound(pos[i]); if (tmp == st.begin()) continue; tmp--; ans++; st.erase(tmp); } } writeln(ans); return 0; }
【D】Two Sequences
【思路要点】
- 对于结果的每一位,我们分别确定它是0还是1。
- 假设我们当前想要确定结果在\(2^p\)上的数位,现在问题转化为了,在\(N^2\)个和中,有多少个在\(2^p\)位上是1。
- 我们可以将数和加法放到模\(2^{p+1}\)意义下去考虑,枚举一个\(b\)中的数\(b_i\),我们希望统计模意义下区间\([2^p-b_i,2^{p+1}-b_i)\)内\(a\)数组中元素的个数。
- 先对\(a\)数组排序,询问时在上面二分即可。
- 时间复杂度\(O(NLogNLogV)\)。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 200005; const int MAXLOG = 30; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } int n, a[MAXN], b[MAXN], c[MAXN]; int getcnt(int x, int y) { return lower_bound(c + 1, c + n + 1, y + 1) - lower_bound(c + 1, c + n + 1, x); } int main() { read(n); for (int i = 1; i <= n; i++) read(a[i]); for (int i = 1; i <= n; i++) read(b[i]); int ans = 0; for (int p = 1; p <= MAXLOG; p++) { int tmp = 1 << p, tnp = 1 << (p - 1); for (int i = 1; i <= n; i++) c[i] = b[i] % tmp; sort(c + 1, c + n + 1); int cnt = 0; for (int i = 1; i <= n; i++) { int now = a[i] % tnp; if (a[i] & tnp) cnt += n - getcnt(tnp - now, tmp - 1 - now); else cnt += getcnt(tnp - now, tmp - 1 - now); } ans += (cnt & 1) * tnp; } writeln(ans); return 0; }
【E】Both Sides Merger
【思路要点】
- 数组中最后留下的数一定是原数组中若干个数的和。
- 考虑如何选取一个能够通过适当的操作恰好留下集合中所有数的和的集合。
- 假设当前同时选定了\(a_i\)和\(a_j\),那么在第\(i\)位之前操作或第\(j\)位之后操作均不可能将\(a_i\)和\(a_j\)合并,而在它们中间操作必然会使它们之间的距离减少2,因此若\(i\)和\(j\)奇偶性不同,我们无法通过合适的操作将它们合并在一起。
- 反过来,我们发现如果选取的集合中所有数的坐标奇偶性相同,我们可以通过不断地对相邻的两个被选取的数坐标的中点进行操作,直至将所选集合全部合并。
- 那么,枚举所选集合坐标的奇偶性,取合法坐标上的正数即可。
- 时间复杂度\(O(N^2)\)(或\(O(N)\))。
- 注意特判所有数都是负数的情况。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 1005; const long long INF = 1e18; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } int n, from, a[MAXN]; long long ans; int main() { read(n); for (int i = 1; i <= n; i++) read(a[i]); ans = -INF; for (int i = 1; i <= n; i++) { long long now = 0; for (int j = i; j <= n; j += 2) if (a[j] > 0 || i == j) now += a[j]; if (now > ans) { ans = now; from = i; } } writeln(ans); static int tmp[MAXN]; int tot = 0; for (int i = from; i <= n; i += 2) if (a[i] > 0 || i == from) tmp[++tot] = i; writeln(tmp[1] - 1 + n - tmp[tot] + (tmp[tot] - tmp[1]) / 2); for (int i = n; i >= tmp[tot] + 1; i--) writeln(i); for (int i = tot - 1; i >= 1; i--) { int pos = (tmp[i] + tmp[i + 1]) / 2; for (int j = pos; j > tmp[i]; j--) writeln(j); } for (int i = tmp[1] - 1; i >= 1; i--) writeln(1); return 0; }
【F】Two Faced Edges
【思路要点】
- 考虑翻转一条边\(a\Rightarrow b\)会使强联通分量如何变化。
- 首先,我们断开了一条边\(a\Rightarrow b\),若原本\(b\)可以到达\(a\),但\(a\)无法在不通过\(a\Rightarrow b\)的情况下到达\(b\),那么强联通分量的个数将会增加。
- 其次,我们新增了一条边\(b\Rightarrow a\),若原本\(b\)无法到达\(a\),而\(a\)可以在不通过\(a\Rightarrow b\)的情况下到达\(b\),那么强联通分量的个数将会减少。
- 因此,我们需要回答的问题本质上是:
- 1、对于每一条边\(a\Rightarrow b\),确定\(b\)是否可以到达\(a\)。
- 2、对于每一条边\(a\Rightarrow b\),确定\(a\)是否可以在不通过\(a\Rightarrow b\)的情况下到达\(b\)。
- 对于1,我们从每个点开始做一遍DFS即可确定每个点是否能走到其它点。
- 对于2,我们枚举\(a\),并试图在\(O(M)\)的时间内确定\(a\)的每一条出边的答案。
- 不妨令\(a\)的出点为\(b_1,b_2,...,b_m\),我们进行下列操作:
- 将从\(b_1\)出发,能够不经过\(a\)而到达的点标记为1。
- 将从\(b_2\)出发,能够不经过\(a\)和被标记过的点而到达的点标记为2。
- ……
- 将从\(b_m\)出发,能够不经过\(a\)和被标记过的点而到达的点标记为\(m\)。
- 按照\(b_m,...,b_2,b_1\)的顺序再进行一遍标号。
- 两次标号相同的点(显然若两次标号相同,标号即为其下标)就是\(a\)不可以在不通过\(a\Rightarrow b\)的情况下到达\(b\)的点,其余的就是\(a\)可以在不通过\(a\Rightarrow b\)的情况下到达\(b\)的点。
- 时间复杂度\(O(N*M)\)。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 1005; const int MAXM = 2e5 + 5; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } struct edge {int dest, home; }; int n, m; int pre[MAXN], suf[MAXN]; bool vis[MAXN][MAXN], ans[MAXM]; vector <edge> a[MAXN]; void dfs(int from, int pos) { vis[from][pos] = true; for (unsigned i = 0; i < a[pos].size(); i++) if (!vis[from][a[pos][i].dest]) dfs(from, a[pos][i].dest); } void dfspre(int pos, int val) { pre[pos] = val; for (unsigned i = 0; i < a[pos].size(); i++) if (pre[a[pos][i].dest] == -1) dfspre(a[pos][i].dest, val); } void dfssuf(int pos, int val) { suf[pos] = val; for (unsigned i = 0; i < a[pos].size(); i++) if (suf[a[pos][i].dest] == -1) dfssuf(a[pos][i].dest, val); } int main() { read(n), read(m); for (int i = 1; i <= m; i++) { int x, y; read(x), read(y); a[x].push_back((edge) {y, i}); } for (int i = 1; i <= n; i++) dfs(i, i); for (int i = 1; i <= n; i++) for (unsigned j = 0; j < a[i].size(); j++) if (vis[a[i][j].dest][i]) ans[a[i][j].home] ^= true; for (int i = 1; i <= n; i++) { memset(pre, -1, sizeof(pre)); memset(suf, -1, sizeof(suf)); pre[i] = suf[i] = 0; for (unsigned j = 0; j < a[i].size(); j++) if (pre[a[i][j].dest] == -1) dfspre(a[i][j].dest, j); for (unsigned j = a[i].size(); j > 0; j--) if (suf[a[i][j - 1].dest] == -1) dfssuf(a[i][j - 1].dest, j - 1); for (unsigned j = 0; j < a[i].size(); j++) if (pre[a[i][j].dest] != suf[a[i][j].dest]) ans[a[i][j].home] ^= true; } for (int i = 1; i <= m; i++) if (ans[i]) printf("diff\n"); else printf("same\n"); return 0; }