Overview:
ACdream 群【第三次原创群赛】将于【11月17日星期六晚19点】进行。本次原创群赛由[sjtu]ftiasch(人称 GXX 神牛、叉妹)出题。欢迎大家积极参与~【冠军】和【ACdreamOJ第10000次提交】的ID我们将会赠送小礼品一份 。。 比赛传送门。。。。
(。。。看样子叉娘不愿意在 blog 里贴太学术的东西。。。以下授权转发。。。
ac-dream-3rd.tar (题目、标程、解题报告、测试数据。。。
Official Solutions:
Xor
考虑第\(i\)个二进制位,因为\(n\)是奇数,所以\(A\)中第\(i\)位\(0\)和\(1\)的个数不相等(对于\(B\)也成立)。于是可以确定\(x\)的第\(i\)位,最后再验证\(x\)是否合法即可。
Triangle
由叉积的表达式,知道面积只跟坐标的奇偶性有关,枚举每个点的类型即可。
Tranform
广搜的复杂度是… \[N(1 + \frac{1}{2} + \cdots + \frac{1}{N}) = O(N \log N)\].
Product
\[v(A) = \frac{\prod_{a \in A}(1 + a) – \prod_{a \in A}(1 – a)}{2}\]
Permanent
状压动态规划。
Path
如果 \(x\) 是可行的,那么 \((x – 2)\) 也是可行的,所以可以动态规划求出最长的奇数和偶数。注意负数的情况。
#include <cassert> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N = 100000; int n, q, parent[N]; int find(int i) { return parent[i] == -1 ? i : (parent[i] = find(parent[i])); } int edge_count, first_edge[N], to[N << 1], length[N << 1], next_edge[N << 1]; const int INF = 100000000; int down[N][2], limit[2]; void add_edge(int u, int v, int w) { to[edge_count] = v; length[edge_count] = w; next_edge[edge_count] = first_edge[u]; first_edge[u] = edge_count ++; } #define UPDATE(x, a) x = max(x, a) void dfs(int p, int u) { down[u][0] = 0; down[u][1] = -INF; for (int iter = first_edge[u]; iter != -1; iter = next_edge[iter]) { int v = to[iter]; int l = length[iter]; if (v != p) { dfs(u, v); for (int x = 0; x < 2; ++ x) { for (int y = 0; y < 2; ++ y) { UPDATE(limit[x + y + l & 1], down[u][x] + down[v][y] + l); } } for (int y = 0; y < 2; ++ y) { UPDATE(down[u][y + l & 1], down[v][y] + l); } } } } int main() { assert(scanf("%d%d", &n, &q) == 2); assert(1 <= n && n <= N); assert(1 <= q && q <= N); memset(parent, -1, sizeof(parent)); edge_count = 0; memset(first_edge, -1, sizeof(first_edge)); for (int i = 0; i < n - 1; ++ i) { int a, b, c; assert(scanf("%d%d%d", &a, &b, &c) == 3); assert(1 <= a && a <= n); assert(1 <= b && b <= n); assert(1 <= c && c <= 2); a --; b --; assert(find(a) != find(b)); parent[find(a)] = find(b); add_edge(a, b, c); add_edge(b, a, c); } limit[0] = limit[1] = -INF; dfs(-1, 0); while (q --) { int l; assert(scanf("%d", &l) == 1); if (l < 0) { puts("No"); } else { int t = l % 2; puts(l <= limit[t] ? "Yes" : "No"); } } return 0; }
Multiplication
\[C[k] = (A[1] + A[2] + \cdots + A[k]) \cdot B[k] + (B[1] + B[2] + \cdots + B[k]) \cdot A[k] – A[k] \cdot B[k]\]
Matching
利用 Tutte matrix 可以得到很短的\(O(N^3)\)算法。
#include <cassert> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N = 100; const int MOD = 1000000000 + 7; int n, a[N][N]; int inverse(int a) { return a == 1 ? 1 : (long long)(MOD - MOD / a) * inverse(MOD % a) % MOD; } int main() { assert(scanf("%d", &n) == 1); assert(1 <= n && n <= N); for (int i = 0; i < n; ++ i) { for (int j = 0; j < n; ++ j) { assert(scanf("%d", &a[i][j]) == 1); } } for (int i = 0; i < n; ++ i) { assert(a[i][i] == 0); for (int j = 0; j < n; ++ j) { assert(a[i][j] == a[j][i]); assert(a[i][j] == 0 || a[i][j] == 1); } } for (int i = 0; i < n; ++ i) { for (int j = 0; j < n; ++ j) { if (a[i][j]) { a[i][j] = rand() % MOD; a[j][i] = MOD - a[i][j]; } } } int rank = 0; for (int i = 0; i < n; ++ i) { int pivot = rank; while (pivot < n && !a[pivot][i]) { pivot ++; } if (pivot < n) { for (int j = 0; j < n; ++ j) { swap(a[rank][j], a[pivot][j]); } { int times = inverse(a[rank][i]); for (int j = 0; j < n; ++ j) { a[rank][j] = (long long)a[rank][j] * times % MOD; } for (int k = 0; k < n; ++ k) { if (k != rank && a[k][i]) { int times = a[k][i]; for (int j = 0; j < n; ++ j) { (a[k][j] += MOD - (long long)a[rank][j] * times % MOD) %= MOD; } } } } rank ++; } } assert(!(rank & 1)); printf("%d\n", rank >> 1); return 0; }
/* .................................................................................................................................. */ const int N = 369; int P[N], F[N], B[N], Q[N], head, tail; bool G[N][N], InB[N], inQ[N]; int mark[N], tot; int n, s, t, match; #define pre F[P[i]] int lca(int u, int v){ ++tot; RST(mark); for (int i=u;i;i=pre){ i=B[i]; mark[i]=tot; } for (int i=v;i;i=pre){ i=B[i]; if (mark[i]==tot) return i;} } void blossoming(int u,int v){ int z = lca(u, v); RST(InB); for (int i=u;B[i]!=z;i=pre){ if (B[pre]!=z) F[pre]=P[i]; InB[B[i]]=true; InB[B[P[i]]]=true; } for (int i=v;B[i]!=z;i=pre){ if (B[pre]!=z) F[pre]=P[i]; //同理 InB[B[i]]=true; InB[B[P[i]]]=true; } if (B[u]!=z) F[u]=v; if (B[v]!=z) F[v]=u; REP_1(i, n) if (InB[B[i]]){ B[i]=z; if (!inQ[i]){ Q[tail++]=i; inQ[i]=true; } } } void change(){ int x,y,z=t; while (z){ y=F[z], x=P[y]; P[y]=z, P[z]=y, z=x; } } bool bfs(){ RST(F, inQ); REP_1(i, n) B[i] = i; head=0; tail=1, Q[0]=s, inQ[s]=1; while (head < tail){ int u=Q[head++]; REP_1(v, n) if (G[u][v] && B[u]!=B[v] && P[u]!=v){ if (s==v || P[v] && F[P[v]]) blossoming(u,v); else if (!F[v]){ F[v] = u; if (P[v]){ Q[tail++] = P[v]; inQ[P[v]] = true; } else{ t = v, change(); return true; } } } } return false; } int EBC(){ int match = 0; RST(P); REP_1_N(s, n) if (!P[s] && bfs()) ++match; return match; } void init(){ RD(n); REP_2_1(i, j, n, n) RD(G[i][j]); } int main(){ #ifndef ONLINE_JUDGE freopen("in.txt", "r", stdin); #endif init(); OT(EBC()); }
Cut
一条边可以切开,当且仅当两边联通块都是偶数。
Component
考虑联通块不包括重心,则最多只能有\(\frac{n}{2}\)的大小。于是就算包含重心的联通块的最小权值,有一个熟知的动态规划算法可以做到\(O(N^2)\)。之后点分治即可。复杂度仍然是\(O(N^2)\)。
"熟知的动态规划算法"可以参见何森的集训队论文。
#include <cassert> #include <cstdio> #include <cstring> #include <vector> #include <algorithm> using namespace std; const int N = 2000; const int INF = 1000000000; int n, weight[N], parent[N], answer[N + 1]; vector <int> adjacent[N]; int find(int i) { return parent[i] == -1 ? i : find(parent[i]); } bool mark[N]; int size[N], max_tree[N]; vector <int> nodes; int minimum[N + 1][N + 1]; #define foreach(i, v) for (__typeof((v).begin()) i = (v).begin(); i != (v).end(); ++ i) void dfs(int p, int u) { size[u] = 1; max_tree[u] = 0; nodes.push_back(u); foreach (iter, adjacent[u]) { int v = *iter; if (v != p && !mark[v]) { dfs(u, v); size[u] += size[v]; max_tree[u] = max(max_tree[u], size[v]); } } } void divide(int root) { nodes.clear(); dfs(-1, root); int total = size[root]; int new_root = -1; int min_cost = INF; foreach (iter, nodes) { int u = *iter; int ret = max(total - size[u], max_tree[u]); if (ret < min_cost) { new_root = u; min_cost = ret; } } nodes.clear(); dfs(-1, new_root); int m = nodes.size(); minimum[m][0] = 0; for (int i = 1; i <= m; ++ i) { minimum[m][i] = INF; } for (int i = m - 1; i >= 0; -- i) { int id = nodes[i]; for (int j = 0; j <= m; ++ j) { minimum[i][j] = minimum[i + size[id]][j]; if (j) { minimum[i][j] = min(minimum[i][j], minimum[i + 1][j - 1] + weight[id]); } } } for (int j = 1; j <= m; ++ j) { answer[j] = min(answer[j], minimum[1][j - 1] + weight[new_root]); } mark[new_root] = true; foreach (iter, adjacent[new_root]) { int v = *iter; if (!mark[v]) { divide(v); } } } int main() { assert(scanf("%d", &n) == 1); assert(1 <= n && n <= N); for (int i = 0; i < n; ++ i) { assert(scanf("%d", weight + i) == 1); assert(1 <= weight[i] && weight[i] <= 1000000); } memset(parent, -1, sizeof(parent)); for (int i = 0; i < n - 1; ++ i) { int a, b; assert(scanf("%d%d", &a, &b) == 2); a --; b --; adjacent[a].push_back(b); adjacent[b].push_back(a); assert(find(a) != find(b)); parent[find(a)] = find(b); } for (int i = 1; i <= n; ++ i) { answer[i] = INF; } memset(mark, 0, sizeof(mark)); divide(0); for (int i = 1; i <= n; ++ i) { printf("%d%c", answer[i], i == n ? '\n' : ' ' ); } return 0; }
/* .................................................................................................................................. */ const int N = 3009; int A[N], f[N], dp[N][N]; VI adj[N]; bool vis[N]; int sz[N]; int n, res; #define v adj[u][i] void dfs(int u){ vis[u] = true, sz[u] = 1; FLC(dp[u], 0x3f), dp[u][1] = A[u]; REP(i, SZ(adj[u])) if (!vis[v]){ dfs(v), sz[u] += sz[v]; DWN_1(j, sz[u], 2) DWN_1(k, min(sz[v], j-1), 1) // 剪枝 .. . checkMin(dp[u][j], dp[u][j-k] + dp[v][k]); } REP_1(i, sz[u]) checkMin(f[i], dp[u][i]); } int main(){ #ifndef ONLINE_JUDGE freopen("in.txt", "r", stdin); #endif REP_1_C(i, RD(n)) RD(A[i]); FLC(f, 0x3f); DO(n-1){ int x, y; RD(x, y); adj[x].PB(y), adj[y].PB(x); } dfs(1); REP_1(i, n-1) OT(f[i]); cout << f[n] << endl; }