和[POJ 3417] Network这道题差不多。
LCA + 树上差分。 统计每个节点和它父亲相连的边被覆盖的次数,那切割了这条边形成最小割,还需要切割被覆盖次数条副边。
求最小值即可。

#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int maxn = 2e5+50; typedef long long ll; ///加边 int cnt, h[maxn]; struct edge { int to, pre, v; } e[maxn << 1]; void init() { cnt = 0; memset(h, 0, sizeof(h)); } void add(int from, int to, int v) { cnt++; e[cnt].pre = h[from]; ///5-->3-->1-->0 e[cnt].to = to; e[cnt].v = v; h[from] = cnt; } ///LCA int dist[maxn]; int dep[maxn]; int anc[maxn][33]; ///2分的父亲节点 void dfs(int u, int fa) { for(int i = h[u]; i; i = e[i].pre) { int v = e[i].to; if(v == fa) continue; dist[v] = dist[u] + e[i].v; dep[v] = dep[u] + 1; anc[v][0] = u; dfs(v, u); } } void LCA_init(int n) { for(int j = 1; (1 << j) < n; j++) for(int i = 1; i <= n; i++) if(anc[i][j-1]) anc[i][j] = anc[anc[i][j-1]][j-1]; } int LCA(int u, int v) { int log; if(dep[u] < dep[v]) swap(u, v); for(log = 0; (1 << log) < dep[u]; log++); for(int i = log; i >= 0; i--) if(dep[u] - (1 << i) >= dep[v]) u = anc[u][i]; if(u == v) return u; for(int i = log; i >= 0; i--) if(anc[u][i] && anc[u][i] != anc[v][i]) u = anc[u][i], v = anc[v][i]; return anc[u][0]; } int w[maxn]; int sum[maxn]; int dfs2(int u, int fa) { sum[u] += w[u]; for(int i = h[u]; i; i = e[i].pre) { int v = e[i].to; if(v == fa) continue; sum[u] += dfs2(v, u); } return sum[u]; } int num[maxn]; int main() { int T, kase = 0; scanf("%d", &T); while(T--) { init(); int n, m; scanf("%d %d", &n, &m); for(int i = 1; i < n; i++) { int u, v; scanf("%d %d", &u, &v); add(u, v, 1), add(v, u, 1); } dfs(1, -1); LCA_init(n); memset(w, 0, sizeof(w)); for(int i = 1; i <= m - n + 1; i++) { int u, v; scanf("%d %d", &u ,&v); w[u] += 1, w[v] += 1; // printf("%d ", LCA(u, v)); w[LCA(u, v)] += -2; } memset(sum, 0, sizeof(sum)); dfs2(1, -1); int ans = 1e9 + 7; for(int i = 2; i <= n; i++) { num[i] = sum[i] + 1; ans = min(ans, num[i]); } printf("Case #%d: %d ",++kase, ans); } return 0; }
明天开始强化数据结构啦。
我记得之前的写法都是分成三个域来做,这次用边带权并查集。

#include <iostream> #include <algorithm> #include <cstring> #include <cstdio> using namespace std; const int maxn = 5e4 + 50; int pre[maxn]; int d[maxn]; int Find(int x) { if(x == pre[x]) return x; int root = Find(pre[x]); d[x] += d[pre[x]]; return pre[x] = root; } int main() { int n, k; scanf("%d %d", &n, &k); for(int i = 1; i <= n; i++) pre[i] = i, d[i] = 0; int ans = 0; for(int i = 1; i <= k; i++) { int tmp, x, y; scanf("%d %d %d", &tmp, &x, &y); if(x > n || y > n) { ans++; continue; } if(x == y && tmp == 2) { ans++; continue; } int cnt = (tmp == 1 ? 0 : 1); int u = Find(x), v = Find(y); if(u != v) ///在同一个集合 { d[u] = ((d[y]) % 3 + cnt + 3 - (d[x] % 3)) % 3; pre[u] = v; } else ///在同一个集合 { if(cnt == 0 && (abs(d[y] - d[x]) % 3 != 0)) ans++; if(cnt == 1 && !((d[x] - d[y]) % 3 == 1 || (d[y] - d[x]) % 3 == 2)) ans++; } } printf("%d ", ans); return 0; }
牛客练习赛16
没仔细思考,就上手写代码,结果就是走了个大弯路。
自己写的代码:

#include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 50; char s[maxn]; struct node { int c; int id; friend bool operator < (node A, node B) { if(A.c != B.c) return A.c > B.c; else return A.id < B.id; } }; node cur[maxn]; node tmp[maxn]; int main() { scanf("%s", s); int len = strlen(s); for(int i = 0; i < len; i++) { cur[i].c = s[i] - 'a'; cur[i].id = i; } int ans = 0; while(1) { memcpy(tmp, cur, sizeof(tmp)); sort(tmp + ans, tmp + len); int maxv = tmp[ans].c; for(int i = ans; i < len; i++) { if(maxv == tmp[i].c) { printf("%c", tmp[i].c + 'a'); ans = tmp[i].id + 1; } else break; } if(ans > len - 1) break; } puts(""); return 0; }
当自己写完后,发现了规律,就是最后答案的串,字符串的一定是从大到小的,比如bbba。
看完某一的代码:

#include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 50; char s[maxn]; int main() { scanf("%s", s + 1); int n = strlen(s + 1); string ans = ""; ans += s[n]; for(int i = n - 1; i >= 1; i--) { if(s[i] >= ans[ans.length() - 1]) ans += s[i]; } reverse(ans.begin(), ans.end()); cout << ans << endl; return 0; }
B漂亮的树
这个题我一直想着用并查集来做:

#include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 50; int pre[maxn]; int d[maxn]; int num[maxn]; int Find(int x) { if(x == pre[x]) return x; int root = Find(pre[x]); d[x] += d[pre[x]]; return pre[x] = root; } void merge1(int x, int y) { x = Find(x), y = Find(y); if(x != y) { pre[x] = y, d[x] = num[y]; num[y] += num[x]; } } struct node { int val; int id; }; node cur[maxn]; int main() { int n; scanf("%d", &n); for(int i = 1; i <= n; i++) pre[i] = i, d[i] = 0, num[i] = 1; for(int i = 1; i <= n; i++) { scanf("%d", &cur[i].val); cur[i].id = i; } for(int i = 1; i <= (n - 1) / 2; i++) { if(cur[i].val + 1 == cur[i + 1].val) merge1(i, i + 1); } for(int i = n; i > (n + 2) / 2; i--) { if(cur[i].val + 1 == cur[i - 1].val) merge1(i , i - 1); } for(int i = 1; i <= n / 2; i++) { if(cur[i].val == cur[n - i + 1].val) merge1(i, n - i + 1); } int maxv = 0; for(int i = 1; i <= n; i++) { //printf("%d ", num[i]); maxv = max(maxv, num[i]); } printf("%d ", n - maxv); return 0; } /* 4 1 2 2 3 11 1 2 3 8 5 7 6 5 4 2 1 */
我是把递增上升的符合规律的联成一个块,但是有一种情况没法解决, 1 2 3 8 5 7 6 5 4 2 1,就是1 2 3和8虽然不是等差1的递增规律,但和5是,我在想着先把小的合并,但是还是不行。
看别人的题解,我发现我忽视了每个val和它的位置之间的关系,就是如果等差为1递增,val - i是相同的,以(n+1) / 2为例。
比如上面的样例,是0 0 0 4 0 1 1 1 1 0 0,这样就相当于把5也合并到里面了。

#include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 50; map<int, int> vis; int a[maxn]; int main() { int n; scanf("%d", &n); for(int i = 1; i <= n; i++) scanf("%d", &a[i]); for(int i = 1; i <= n / 2; i++) { a[i] -= i; a[n - i + 1] -= i; vis[a[i]]++; vis[a[n - i + 1]]++; } if(n % 2) a[(n + 1) / 2] -= (n + 1) / 2, vis[a[(n + 1) / 2]]++; int ans = 0; for(map<int, int>::iterator it = vis.begin(); it != vis.end(); it++) { ans = max(ans, it->second); } printf("%d ", n - ans); return 0; }
C任意点
联通块的个数 - 1。

#include <bits/stdc++.h> using namespace std; const int maxn = 1e2 + 50; int x[maxn], y[maxn]; int pre[maxn]; int Find(int x) { return x == pre[x] ? x : pre[x] = Find(pre[x]); } void merge1(int x, int y) { x = Find(x); y = Find(y); pre[x] = y; } int main() { int n; scanf("%d", &n); for(int i = 1; i <= n; i++) { pre[i] = i; scanf("%d %d", &x[i], &y[i]); } for(int i = 1; i <= n; i++) { for(int j = i + 1; j <= n; j++) { if(x[i] == x[j] || y[i] == y[j]) merge1(i, j); } } int cnt = 0; for(int i = 1; i <= n; i++) { if(pre[i] == i) cnt++; } printf("%d ", cnt - 1); return 0; } /* 1 2 3 4 5 */
E 求值
比赛的时候我想的是每个数字拆成20位来搞,用前缀和什么的搞一搞,想起去年西安赛区有道这样的题,稍后补一下,
看了题解后明白了,实际上暴力做法n^2就是枚举区间,但是通过 | 运算,区间中通过或运算能产生不同的数的个数最多为20,可以用set来维护,总时间复杂度O(20log20*n) 某一还是sao,看了一个别人的题解,20位拆开考虑,代码贼长
他的思路总体考虑:

#include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 50; int a[maxn]; set<int> ans, tmp, del; int main() { int n; scanf("%d", &n); for(int i = 1; i <= n; i++) { scanf("%d", &a[i]); ans.insert(a[i]); int res = a[i]; for(set<int>::reverse_iterator it = tmp.rbegin(); it != tmp.rend(); it++) { if((a[*it] | res) != res) res |= a[*it], ans.insert(res); else del.insert(*it); } for(set<int>::iterator it = del.begin(); it != del.end(); it++) { tmp.erase(*it); } del.clear(); tmp.insert(i); } printf("%d ", ans.size()); return 0; }
F选值

#include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 50; int a[maxn]; typedef long long ll; int main() { int n, d; scanf("%d %d", &n, &d); for(int i = 1; i <= n; i++) scanf("%d", &a[i]); ll ans = 0; for(int i = n; i >= 1; i--) { int j = lower_bound(a + 1, a + n + 1, a[i] - d) - a; if(j + 2 <= i) ans += (ll)(i - j - 1) * (i - j) / 2LL; } printf("%lld ", ans); return 0; } /* 1 2 3 4 5 */