【題目連接】node
A - Wasserstein Distanceios
模擬。從左往右填充每個,若是某一個格子不足,須要從右邊離他最近的有盈餘的格子裏拿一些來填充;若是某一個格子有盈餘,那麼多餘部分往右扔過去。c++
/******************************* Judge Result : AC *******************************/ #include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 10; const int INF = 0x7FFFFFFF; int T, n; long long a[maxn], b[maxn]; int main() { #ifdef ZHOUZHENTAO freopen("test.in", "r", stdin); #endif scanf("%d", &T); while(T --) { scanf("%d", &n); for(int i = 1; i <= n; i ++) { scanf("%lld", &a[i]); } for(int i = 1; i <= n; i ++) { scanf("%lld", &b[i]); a[i] = a[i] - b[i]; } long long ans = 0; int p = 1; for(int i = 1; i <= n; i ++) { if(a[i] == 0) continue; if(a[i] > 0) ans += a[i], a[i + 1] += a[i], a[i] = 0; else { while(1) { while(a[p] <= 0) p ++; if(a[i] + a[p] >= 0) { ans = ans + (p - i) * (-a[i]); a[p] += a[i]; a[i] = 0; break; } else { a[i] = a[i] + a[p]; ans = ans + (p - i) * a[p]; a[p] = 0; } } } } printf("%lld\n", ans); } return 0; }
B - 合約數數組
因爲是處理子樹問題,因此能夠將樹轉成 dfs 序,而後就變成了區間問題。而後就是詢問區間上有幾個合約數,莫隊操做一下就能夠了。網絡
#include <cstdio> #include <cmath> #include <cstring> #include <string> #include <vector> #include <algorithm> using namespace std; #define LL long long const int maxn = 2e5 + 10; vector<int> g[maxn]; int pri[maxn]; int T, n, root; int a[maxn], sz; int L[maxn], R[maxn]; int h[maxn], nx[maxn], to[maxn], cnt; int val[maxn], pos[maxn]; int b[maxn], f[maxn]; struct point { int id, l, r; }s[maxn]; bool cmp(const point& a, const point& b) { if(pos[a.l] == pos[b.l]) return a.r < b.r; return pos[a.l] < pos[b.l]; } int prime(int x) { if(x == 1) return 0; for(int i = 2; i * i <= x; i ++) { if(x % i == 0) return 0; } return 1; } void init() { for(int i = 1; i <= 10000; i ++) { pri[i] = prime(i); } for(int i = 4; i <= 10000; i ++) { if(pri[i]) continue; for(int j = 4; j <= i; j ++) { if(i % j) continue; if(pri[j]) continue; g[i].push_back(j); } } } void dfs(int x, int fa) { sz ++; a[sz] = x; L[x] = sz; for(int i = h[x]; i != -1; i = nx[i]) { if(to[i] == fa) continue; dfs(to[i], x); } R[x] = sz; } void add(int x, int y) { to[cnt] = y; nx[cnt] = h[x]; h[x] = cnt ++; } void Delete(int x) { b[val[a[x]]] --; } void Insert(int x) { b[val[a[x]]] ++; } int main() { init(); scanf("%d", &T); while(T --) { scanf("%d %d", &n, &root); int sqr = (int)sqrt(1.0 * n); for(int i = 1; i <= n; i ++) { pos[i] = i / sqr; } for(int i = 1; i <= 10000; i ++) { b[i] = 0; } cnt = 0; for(int i = 1; i <= n;i ++) { h[i] = -1; } for(int i = 0; i < n - 1; i ++) { int x, y; scanf("%d %d", &x, &y); add(x, y); add(y, x); } sz = 0; dfs(root, -1); /* for(int i = 1; i <= n; i ++) { printf("%d ", a[i]); } printf("\n"); for(int i = 1; i <= n; i ++) { printf("!!! %d %d %d\n", i, L[i], R[i]); } printf("ok\n"); */ for(int i = 1; i <= n; i ++) { scanf("%d", &val[i]); } for(int i = 1; i <= n; i ++) { s[i].id = i; s[i].l = L[i]; s[i].r = R[i]; } sort(s + 1, s + 1 + n, cmp); /* for(int i = 1; i <= n; i ++) { printf("q : %d %d %d\n", s[i].id, s[i].l, s[i].r); } */ for(int i = s[1].l; i <= s[1].r; i ++) { Insert(i); } f[s[1].id] = 0; for(int j = 0; j < g[val[s[1].id]].size(); j ++) { f[s[1].id] = f[s[1].id] + b[g[val[s[1].id]][j]]; } int left = s[1].l, right = s[1].r; for(int i = 2; i <= n; i ++) { while(left > s[i].l) left --, Insert(left); while(right < s[i].r) right ++, Insert(right); while(left < s[i].l) Delete(left), left ++; while(right > s[i].r) Delete(right), right --; f[s[i].id] = 0; for(int j = 0; j < g[val[s[i].id]].size(); j ++) { f[s[i].id] = f[s[i].id] + b[g[val[s[i].id]][j]]; } } /* for(int i = 1; i <= n; i ++) { printf("!!! %d : %d\n", i, f[i]); } */ long long mod = 1e9 + 7; long long ans = 0; for(int i = 1; i <= n; i ++) { long long tmp = 1LL * i * f[i] % mod; ans = (ans + tmp) % mod; } printf("%lld\n", ans); } return 0; } /* 100 13 10 10 2 3 1 4 11 4 12 4 13 10 4 2 5 10 3 3 8 3 9 2 6 2 7 30 60 50 24 5 10 12 25 10 120 2 3 4 */
C - 序列變換ui
枚舉全排列,計算每一種排列須要的操做次數。從一個排列轉換成另外一個排列,能夠模擬搞。從一個數字轉換成另外一個數字,只能選擇一種方式,所以很簡單。spa
/******************************* Judge Result : AC *******************************/ #include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 10; const int INF = 0x7FFFFFFF; int T, n; int a[maxn], b[maxn]; int cost[20][20]; int p[maxn]; int u[maxn]; int f[maxn]; int work(int x, int y) { if(x == y) return 0; if(x < y) swap(x, y); int sum = 0; while(1) { if(x % 2 == 0) { if(x / 2 >= y) sum ++, x = x / 2; else return sum + x - y; } else { if(x == y) return sum; else sum ++, x --; } } return sum; } int get() { int sum = 0; for(int i = 1; i <= n; i ++) { u[i] = p[i]; } for(int i = 1; i <= n; i ++) { if(u[i] == i) continue; sum ++; for(int j = i + 1; j <= n; j ++) { if(u[j] == i) { swap(u[i], u[j]); break; } } } return sum; } int main() { #ifdef ZHOUZHENTAO freopen("test.in", "r", stdin); #endif scanf("%d", &T); while(T --) { scanf("%d", &n); for(int i = 1; i <= n; i ++) { scanf("%d", &a[i]); } for(int i = 1; i <= n; i ++) { scanf("%d", &b[i]); } for(int i = 1; i <= n; i ++) { for(int j = 1; j <= n; j ++) { cost[i][j] = work(a[i], b[j]); //cout << a[i] << " -> " << b[j] << " : " << cost[i][j] << endl; } } for(int i = 1; i <= n; i ++) { p[i] = i; } int ans = 2e9; do { int tmp = get(); for(int i = 1; i <= n; i ++) { tmp = tmp + cost[p[i]][i]; } ans = min(ans, tmp); } while(next_permutation(p + 1, p + 1 + n)); printf("%d\n", ans); } return 0; }
D - 數字遊戲code
大體思路爲若是不管 $n_1$ 爲多少,都有一個 $n_2$ 可以找到,使得結果是 mod 的倍數,則先手輸;不然後手輸。因爲 mod 範圍不大,因此能夠枚舉一下 $n_2$ 的位數,而後暴力枚舉處理,只要枚舉 mod 範圍內便可,由於取模以後的數不會超過 mod。blog
E - 小Y吃蘋果遊戲
答案是 2 的 $n$ 次方。
#include <stdio.h> int main() { int n; scanf("%d", &n); printf("%d\n", 1 << n); return 0; }
F - 1 + 2 = 3?
找找規律能夠發現知足條件的數字是二進制上沒有相鄰的 1。所以能夠二分答案,而後數位 dp 計算方案數。
#include <iostream> #include <cstdio> #include <cmath> #include <cstring> #include <queue> using namespace std; #define LL long long LL dp[110][2]; LL a[110]; LL n; LL dfs(int len,int sta,bool limit) { if(len<0) return 1; if(dp[len][sta]!=-1&&!limit) return dp[len][sta]; int up=limit?a[len]:1; LL ans=0; for(int i=0; i<=up; i++) { if(sta&&i==1) continue; ans+=dfs(len-1,i==1,limit&&i==up); } return limit?ans:dp[len][sta]=ans; } LL solve(LL x) { memset(dp,-1,sizeof dp); int cnt=0; while(x>0) { a[cnt++]=x%2; x/=2; } return dfs(cnt-1,0,1); } int main() { int T; scanf("%d", &T); while(T--) { scanf("%lld", &n); LL l = 1, r = 1e18; LL mid, ans; while(l <= r) { mid = (l + r) / 2; LL t = solve(mid) - 1; if(t < n) { l = mid + 1; } else { ans = mid; r = mid - 1; } } printf("%lld\n", ans); } return 0; }
G - 小Y作比賽
暫時不會作
H - 小Y與多米諾骨牌
先處理出選擇 $i$ 位置往左倒,最左會使得 $L_i$ 也到下;往右倒,最右會使得 $R_i$ 也倒下。
而後進行 dp,$dp_i$ 表示 $[1, i]$ 都倒下須要的最少操做次數。有兩種途徑,一種是 $[1, j]$ 先倒下,而後選擇 $i$ 往左倒,使得 $[j+1,i]$ 都倒下;另外一種是 $[1, j]$ 先倒下,而後選擇 $j$ 向右倒,使得 $[j+1,i]$ 都倒下。兩種均可以用線段樹來維護來獲得最優解。
/******************************* Judge Result : *******************************/ #include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 10; const int INF = 1e6; int x[maxn], y[maxn]; int T, n; int L[maxn], R[maxn]; int s[2][4 * maxn]; int dp[maxn]; vector<int> g[maxn]; void build(int flag, int val, int l, int r, int rt) { s[flag][rt] = val; if(l == r) return ; int mid = (l + r) / 2; build(flag, val, l, mid, 2 * rt); build(flag, val, mid + 1, r, 2 * rt + 1); } void update(int flag, int op, int pos, int val, int l, int r, int rt) { if(l == r) { s[flag][rt] = val; return; } int mid = (l + r) / 2; if(pos <= mid) update(flag, op, pos, val, l, mid, 2 * rt); else update(flag, op, pos, val, mid + 1, r, 2 * rt + 1); if(op == 0) s[flag][rt] = min(s[flag][2 * rt], s[flag][2 * rt + 1]); else s[flag][rt] = max(s[flag][2 * rt], s[flag][2 * rt + 1]); } int getmin(int flag, int L, int R, int l, int r, int rt) { if(L <= l && r <= R) { return s[flag][rt]; } int mid = (l + r) / 2; int left = INF, right = INF; if(L <= mid) left = getmin(flag, L, R, l, mid, 2 * rt); if(R > mid) right = getmin(flag, L, R, mid + 1, r, 2 * rt + 1); return min(left, right); } int getmax(int flag, int L, int R, int l, int r, int rt) { if(L <= l && r <= R) { return s[flag][rt]; } int mid = (l + r) / 2; int left = 0, right = 0; if(L <= mid) left = getmax(flag, L, R, l, mid, 2 * rt); if(R > mid) right = getmax(flag, L, R, mid + 1, r, 2 * rt + 1); return max(left, right); } int main() { #ifdef ZHOUZHENTAO freopen("test.in", "r", stdin); #endif scanf("%d", &T); while(T --) { scanf("%d", &n); for(int i = 1; i <= n; i ++) { scanf("%d%d", &x[i], &y[i]); } build(0, INF, 1, n, 1); L[1] = 1; update(0, 0, 1, 1, 1, n, 1); for(int i = 2; i <= n; i ++) { update(0, 0, i, i, 1, n, 1); int left = 1, right = i, ans = -1; while(left <= right) { int mid = (left + right) / 2; if(x[mid] > x[i] - y[i]) ans = mid, right = mid - 1; else left = mid + 1; } L[i] = getmin(0, ans, i, 1, n, 1); update(0, 0, i, L[i], 1, n, 1); } build(0, 0, 1, n, 1); R[n] = n; update(0, 1, n, n, 1, n, 1); for(int i = n - 1; i >= 1; i --) { update(0, 1, i, i, 1, n, 1); int left = i, right = n, ans = -1; while(left <= right) { int mid = (left + right) / 2; if(x[mid] < x[i] + y[i]) ans = mid, left = mid + 1; else right = mid - 1; } R[i] = getmax(0, i, ans, 1, n, 1); update(0, 1, i, R[i], 1, n, 1); } /* for(int i = 1; i <= n; i ++) { cout << i << " " << L[i] << " " << R[i] << endl; } */ for(int i = 1; i <= n; i ++) { g[i].clear(); } for(int i = 1; i <= n; i ++) { g[R[i]].push_back(i); } build(0, INF, 0, n, 1); build(1, INF, 0, n, 1); update(0, 0, 0, 0, 0, n, 1); update(1, 0, 0, 0, 0, n, 1); for(int i = 1; i <= n; i ++) { dp[i] = getmin(1, L[i] - 1, i - 1, 0, n, 1) + 1; dp[i] = min(dp[i], getmin(0, 0, i - 1, 0, n, 1) + 1); update(1, 0, i, dp[i], 0, n, 1); update(0, 0, i, dp[i], 0, n, 1); for(int j = 0; j < g[i].size(); j ++) { update(0, 0, g[i][j] - 1, INF, 0, n, 1); } } /* for(int i = 1; i <= n ; i ++) { printf("dp[%d] = %d\n", i, dp[i]); } */ printf("%d\n", dp[n]); } return 0; }
I - 二數
構造一下最大的小於等於 $n$ 的二數,以及最小的大於等於 $n$ 的二數,取小的就是答案。
#include <cstdio> #include <cmath> #include <cstring> #include <string> using namespace std; #define LL long long char s[1000111]; int main() { int T; scanf("%d", &T); while (~scanf("%s", &s)) { int k=strlen(s); if(s[0]=='1'&&k==1){ printf("0\n"); continue; } int flag=0; for(int i=0;i<k;i++){ if((s[i]-'0')%2==1){ if(s[i]=='9'){ flag=-1; break; } for(int j=i+1;j<k;j++){ if(s[j]>'4'){ flag=1; break; } else if(s[j]<'4'){ flag=-1; break; } } if(flag!=0) break; flag=-1; break; } } if(flag==0){ printf("%s",s); } else if(flag==-1){ int i=0; for(i=0;i<k;i++){ if((s[i]-'0')%2==1){ if(s[i]>'1'||i>0) printf("%c",s[i]-1); break; } printf("%c",s[i]); } i++; for(;i<k;i++){ printf("%c",'8'); } } else if(flag==1){ int i=0; for(i=0;i<k;i++){ if((s[i]-'0')%2==1){ printf("%c",s[i]+1); break; } printf("%c",s[i]); } i++; for(;i<k;i++){ printf("%c",'0'); } } printf("\n"); } return 0; }
J - 小Y寫文章
二分答案 $x$,須要驗證答案小於等於 $x$ 可否作到。驗證:能夠考慮爲有 $n + 1$ 個空位,須要 $m$ 個物品去填充。若是某個物品能夠放在某個位置,就連邊,容量爲 1。須要額外增長一個節點,若是某個位置能夠不放物品,這個節點就和那個位置連邊。看看網絡最大流是否是爲 $n+1$ 便可。若是是,則說明存在放置的方案。
/******************************* Judge Result : AC *******************************/ #include <bits/stdc++.h> using namespace std; const int maxn = 500 + 10; const int INF = 0x7FFFFFFF; struct Edge { int from, to, cap, flow; Edge(int u, int v, int c, int f) :from(u), to(v), cap(c), flow(f){} }; vector<Edge>edges; vector<int>G[maxn]; bool vis[maxn]; int d[maxn]; int cur[maxn]; int S, T; void init() { for (int i = 0; i < maxn; i++) G[i].clear(); edges.clear(); } void AddEdge(int from, int to, int cap) { // cout << from << " -> " << to << " " << cap << endl; edges.push_back(Edge(from, to, cap, 0)); edges.push_back(Edge(to, from, 0, 0)); int w = edges.size(); G[from].push_back(w - 2); G[to].push_back(w - 1); } bool BFS() { memset(vis, 0, sizeof(vis)); queue<int>Q; Q.push(S); d[S] = 0; vis[S] = 1; while (!Q.empty()) { int x = Q.front(); Q.pop(); for (int i = 0; i<G[x].size(); i++) { Edge e = edges[G[x][i]]; if (!vis[e.to] && e.cap>e.flow) { vis[e.to] = 1; d[e.to] = d[x] + 1; Q.push(e.to); } } } return vis[T]; } int DFS(int x, int a) { if (x == T || a == 0) return a; int flow = 0, f; for (int &i = cur[x]; i<G[x].size(); i++) { Edge e = edges[G[x][i]]; if (d[x]+1 == d[e.to]&&(f=DFS(e.to,min(a,e.cap-e.flow)))>0) { edges[G[x][i]].flow+=f; edges[G[x][i] ^ 1].flow-=f; flow+=f; a-=f; if(a==0) break; } } if(!flow) d[x] = -1; return flow; } int dinic(int s, int t) { int flow = 0; while (BFS()) { memset(cur, 0, sizeof(cur)); flow += DFS(s, INF); } return flow; } int a[maxn], b[maxn]; int n, m; int check(int limit) { init(); int s = 0; int t = n + m + 2; S = n + m + 3; T = n + m + 4; AddEdge(S, s, INF); AddEdge(t, T, INF); AddEdge(n + m + 5, 0 + 1 + m, 1); AddEdge(n + m + 5, n + 1 + m, 1); for(int i = 1; i < n ; i ++) { if(abs(a[i] - a[i + 1]) <= limit) { AddEdge(n + m + 5, i + 1 + m, 1); } } AddEdge(s, n + m + 5, n + 1 - m); for(int i = 1; i <= m; i ++) { AddEdge(s, i, 1); } for(int i = 1; i <= m; i ++) { for(int j = 0; j <= n; j ++) { if(j == 0) { if(abs(b[i] - a[1]) <= limit) { AddEdge(i, j + 1 + m, 1); } } else if(j < n) { if(abs(b[i] - a[j]) <= limit && abs(b[i] - a[j + 1]) <= limit) { // cout << i << " " << j << endl; AddEdge(i, j + 1 + m, 1); } } else { if(abs(b[i] - a[n]) <= limit) { AddEdge(i, j + 1 + m, 1); } } } } for(int j = 0; j <= n; j ++) { AddEdge(j + 1 + m, t, 1); } if(dinic(S, T) == n + 1) return 1; return 0; } int main() { #ifdef ZHOUZHENTAO freopen("test.in", "r", stdin); #endif int T; scanf("%d", &T); while(T --) { scanf("%d%d", &n, &m); for(int i = 1; i <= n; i ++) { scanf("%d", &a[i]); } for(int i = 1; i <= m; i ++) { scanf("%d", &b[i]); } int left = 0, right = 2e9, ans; while(left <= right) { int mid = (left + right) / 2; if(check(mid)) ans = mid, right = mid - 1; else left = mid + 1; } printf("%d\n", ans); } return 0; }
K - 樹上最大值
有個東西叫線性基:支持插入元素,合併兩個集合,計算集合中選一些數異或值最大的方法。全部操做複雜度均爲 $O(log(value))$。有了這個方法這題就很簡單了。
枚舉全部子樹,A 集合在這個子樹中,B 集合在子樹之外部分。所以能夠轉化爲區間問題,子樹內用樹形dp加上上面那個方法作就行了,子樹外就是兩個區間的並集,用前綴和後綴的並集便可。
/******************************* Judge Result : AC *******************************/ #include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 2; const int INF = 0x7FFFFFFF; struct Base{ int a[30]; void init(){for(int i=0;i<30;i++)a[i]=0;} void up(int &a, int b) {if(b>a)a=b;} void ins(int x){for(int i=29;~i;i--)if(x>>i&1){if(a[i])x^=a[i];else{a[i]=x;break;}}} int ask(){ int t=0; for(int i=29;~i;i--)up(t,t^a[i]); return t; } }; Base merge(const Base& b1, const Base& b2) { Base res = b1; for(int i = 0; i < 30; i ++) { if(b2.a[i]) res.ins(b2.a[i]); } return res; } int A[maxn], sz; int L[maxn], R[maxn]; int h[maxn], nx[2 * maxn], to[2 * maxn], cnt; int n, val[maxn]; Base node[maxn], pre[maxn], suf[maxn]; void dfs(int x, int fa) { sz ++; A[sz] = x; L[x] = sz; node[x].init(); node[x].ins(val[x]); for(int i = h[x]; i != -1; i = nx[i]) { if(to[i] == fa) continue; dfs(to[i], x); node[x] = merge(node[x], node[to[i]]); } R[x] = sz; } void add(int x, int y) { to[cnt] = y; nx[cnt] = h[x]; h[x] = cnt ++; } int main() { #ifdef ZHOUZHENTAO freopen("test.in", "r", stdin); #endif scanf("%d", &n); for(int i = 1; i <= n; i ++) { h[i] = -1; scanf("%d", &val[i]); } for(int i = 0; i < n - 1; i ++) { int x, y; scanf("%d %d", &x, &y); add(x, y); add(y, x); } dfs(1, -1); pre[0].init(); for(int i = 1; i <= n; i ++) { pre[i] = pre[i - 1]; pre[i].ins(val[A[i]]); } suf[n + 1].init(); for(int i = n; i >= 1; i --) { suf[i] = suf[i + 1]; suf[i].ins(val[A[i]]); } int ans = 0; for(int i = 1; i <= n; i ++) { Base tmp = node[i]; Base left, right; left.init(); right.init(); if(L[i] != 1) left = pre[L[i] - 1]; if(R[i] != n) right = suf[R[i] + 1]; ans = max(ans, tmp.ask() + merge(left, right).ask()); } printf("%d\n", ans); return 0; }
L - K序列
$dp_{i,j}$ 表示前 $i$ 個數,和對 $mod$ 取模的結果爲 $j$ 的子序列的最長長度。開個滾動數組就行了。
#include <cstdio> #include <cmath> #include <cstring> #include <string> using namespace std; #define LL long long int num[100111]; int dp[2][10001111]; int n, k; int main() { while (~scanf("%d %d", &n, &k)) { for (int i = 0; i < n; ++ i) { scanf("%d", num + i); num[i] %= k; } int wei = 0; memset(dp, -1, sizeof dp); dp[wei][0] = 0; for (int i = 0; i < n; ++ i) { for (int j = 0; j < k; ++ j) { dp[wei ^ 1][(j + num[i]) % k] = dp[wei][(j + num[i]) % k]; if (dp[wei][j] != -1) dp[wei ^ 1][(j + num[i]) % k] = max(dp[wei][(j + num[i]) % k], dp[wei][j] + 1); } wei ^= 1; } printf("%d\n", dp[wei][0]); } return 0; }