2020.10.03 训练赛补题

    科技2022-07-11  81

    2019-2020 ACM-ICPC Pacific Northwest Regional Contest

    A. Radio Prize

    思路:预处理出三个值:各子树的大小,各子树点权之和,各个节点到1号节点的距离。子树划分是以树根作为划分的依据。那么,距离可以这么推。

    #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn = 100010, maxm = maxn * 2; typedef long long ll; typedef pair<ll, ll> P; int h[maxn], e[maxm], ne[maxm], idx; int N; ll wv[maxn], we[maxm]; ll sz[maxn], sum_wv[maxn], d[maxn], A[maxn], B[maxn], W; void add(int a, int b, int c){ e[idx] = b, ne[idx] = h[a], we[idx] = c, h[a] = idx++; } //第一个返回值是子树大小,第二个返回值是子树点权之和。 P dfs1(int u, int fa){ P p = {1, wv[u]}; for(int i = h[u]; i != -1; i = ne[i]){ int v = e[i]; if(v == fa) continue; d[v] = d[u] + we[i]; P son = dfs1(v, u); p.first += son.first, p.second += son.second; } sz[u] = p.first, sum_wv[u] = p.second; return p; } void dfs2(int u, int fa){ for(int i = h[u]; i != -1; i = ne[i]){ int v = e[i]; if(v == fa) continue; A[v] = A[u] + (N - 2 * sz[v]) * we[i]; B[v] = B[u] + (W - 2 * sum_wv[v]) * we[i]; dfs2(v, u); } } int main(){ memset(h, -1, sizeof h); scanf("%d", &N); for(int i = 1; i <= N; i++){ scanf("%d", &wv[i]); //求所有点权之和。 W += wv[i]; } for(int i = 1; i < N; i++){ int a, b, c; scanf("%d%d%d", &a, &b, &c); add(a, b, c), add(b, a, c); } dfs1(1, -1); for(int i = 1; i <= N; i++){ A[1] += d[i]; B[1] += wv[i] * d[i]; } dfs2(1, -1); for(int i = 1; i <= N; i++){ printf("%lld\n", wv[i] * A[i] + B[i]); } return 0; }

    B. Perfect Flush

    题意:给出n个数 ∈ [ 1 , k ] \in [ 1 , k ] [1,k],找出它的子序列中,字典序最小的 1~k 的全排列。用单调栈去解题(单调增大)。首先统计每个数最后出现的一个位置。然后遍历这个序列,我们称现在遍历到的这个序列的数为 x i x_i xi,做一下操作 如果 x i x_i xi还未出现在答案序列,并且答案序列不为空,就比较 x i x_i xi 和答案序列中最后一个数的大小。如果大于原序列中的最后一个数,直接放到答案序列就行,如果小于,就看后面还是否会出现当前答案序列的最后一个数,如果后面不会再出现了,也直接加到答案序列。如果 x i x_i xi 已经出现在答案序列,跳过。 #include<cstdio> #include<algorithm> using namespace std; const int maxn = 200010; int a[maxn], stk[maxn], p[maxn], N, K; //记录当前数字有没有出现在答案里面。 bool vis[maxn]; int main() { scanf("%d%d", &N, &K); for (int i = 1; i <= N; i++) { scanf("%d", &a[i]); //记录最后一次出现的位置 p[a[i]] = i; } int top = 0; for (int i = 1; i <= N; i++) { if (vis[a[i]]) continue; while (top && a[i] < stk[top] && p[stk[top]] > i) { vis[stk[top]] = false; --top; } stk[++top] = a[i]; vis[a[i]] = true; } for (int i = 1; i <= K; i++) printf("%d%c", stk[i], i == K ? '\n' : ' '); return 0; }

    E. Rainbow Strings

    统计每个字母出现的次数 c n t 1 , c n t 2 , . . . , c n t n cnt_1, cnt_2, ... , cnt_n cnt1,cnt2,...,cntn. 然后答案就是 c n t 1 ∗ c n t 2 ∗ . . . ∗ c n t n cnt_1*cnt_2*...*cnt_n cnt1cnt2...cntn.原因很简单,对于一个字母 a:不选、选第一个位置、选第二个位置……有 cnt + 1 种选择的方法。代码略。

    I. Error Correction

    由于每交换一次就改变了一次排列逆序对的奇偶性,因此这个图没有奇数环,是个二分图。二分图建边一定要注意,之前都学错了。是二分图两个部分,一个部分全部向另一个部分建单向边,这样才能保证匈牙利算法是正确的。 #include<cstdio> #include<iostream> #include<algorithm> #include<cstring> #include<unordered_map> using namespace std; const int maxn = 510, maxm = 125000; int h[maxn], e[maxm], ne[maxm], idx; unordered_map<string, int> id; int N, match[maxn]; bool vis[maxn]; void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; } int rev(string& s) { int len = s.size(); int res = 0; for (int i = 0; i < len; i++) { for (int j = 0; j < i; j++) { if (s[i] < s[j]) res++; } } return res; } bool find(int u) { for (int i = h[u]; i != -1; i = ne[i]) { int v = e[i]; if (vis[v]) continue; vis[v] = true; if (match[v] == 0 || find(match[v])) { match[v] = u; return true; } } return false; } int main() { memset(h, -1, sizeof h); scanf("%d", &N); for (int i = 1; i <= N; i++) { string s; cin >> s; id[s] = i; } for (auto p : id) { string s = p.first; int len = s.size(); for (int i = 0; i < len; i++) { for (int j = 0; j < i; j++) { swap(s[i], s[j]); if (id.count(s) && (rev(s) & 1)) add(id[p.first], id[s]); swap(s[i], s[j]); } } } int ans = 0; /*for (int i = 1; i <= N; i++) { for (int j = h[i]; j != -1; j = ne[j]) { printf("%d %d\n", i, e[j]); } }*/ for (int i = 1; i <= N; i++) { memset(vis, false, sizeof vis); if (find(i)) { ans++; } } printf("%d\n", N - ans); return 0; }

    Maze Connect

    题意:求删掉多少边使得没有封闭图形,而只要存在封闭图形,总能通过删一条边,使得封闭图形的数量减少1。因此本质上还是求封闭图形的数量。思路:其实这个题就是求连通块的个数,难点在于建图,原图没法用,需要建立一个点图,对于原图的每一个字符,将其变为一个2*2的矩阵,’'是从左上到右下的赋值为1,‘/’是从右上到左下的赋值为1,‘.’不变化就是初始值为0.然后遍历这个新图的连通块的个数。注意,所有原本就能走到方格之外的点,会和最外面那一圈0形成一个连通块儿,因此最终答案数就是:连通块儿的数目 - 1. #include<iostream> #include<algorithm> using namespace std; const int maxn = 2010; int field[maxn][maxn]; int N, M; int dx[] = { 0, 0, 1, -1 }, dy[] = { 1, -1, 0, 0 }; void dfs(int x, int y) { field[x][y] = 1; for (int i = 0; i < 4; i++) { int nx = x + dx[i], ny = y + dy[i]; if (nx < 0 || nx > N + 1 || ny < 0 || ny > M + 1 || field[nx][ny]) continue; dfs(nx, ny); } } int main() { scanf("%d%d", &N, &M); char c; for (int i = 1; i <= N; i++) { for (int j = 1; j <= M; j++) { cin >> c; if (c == '\\') field[2 * i - 1][2 * j - 1] = field[2 * i][2 * j] = 1; else if (c == '/') field[2 * i - 1][2 * j] = field[2 * i][2 * j - 1] = 1; } } N *= 2, M *= 2; int ans = 0; for (int i = 1; i <= N; i++) { for (int j = 1; j <= M; j++) { if (field[i][j] == 0) dfs(i, j), ans++; } } cout << ans - 1 << endl; return 0; }
    Processed: 0.040, SQL: 8