模擬賽小結:Gym 102800 The 14th Jilin Provincial Collegiate Programming Contest


比賽鏈接:傳送門

陰差陽錯地又開始訓練了。

lh考研去了,我和hat又坑到了另一個很強的學弟guapi組隊。The 1226465-th prime number 又回來啦。

我和guapi差不多停訓了一年,hat倒是一直在訓練,前段時間cf還上了橙,Orz。

 

回來第一場先練了個吉林的省賽熱熱手(省賽的水題還是挺多的,做得很開心),罰時放到現場賽居然還能拿個亞軍。


 

Problem A. Chord 00:15 (+) Solved by xk (小模擬)

代碼:

#include <bits/stdc++.h>
#define eps 1e-6
#define ls (rt << 1)
#define rs (rt << 1 | 1)
#define lowbit(x) (x & (-x))
#define SZ(v) ((int)(v).size())
#define All(v) (v).begin(), (v).end()
#define mp(x, y) make_pair(x, y)
#define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define endl '\n'
using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<int, int> P; const int N = 1e3 + 10; const int mod = 1e9 + 7; const int inf = 0x3f3f3f3f; int main() { map<string, int> mp; mp["C"] = 1; mp["C#"] = 2; mp["D"] = 3; mp["D#"] = 4; mp["E"] = 5; mp["F"] = 6; mp["F#"] = 7; mp["G"] = 8; mp["G#"] = 9; mp["A"] = 10; mp["A#"] = 11; mp["B"] = 12; fast; int T; cin >> T; while(T--) { string a, b, c; cin >> a >> b >> c; int x = mp[a], y = mp[b], z = mp[c]; if(x > y) y += 12; if(y > z) z += 12; if(y - x == 4 && z - y == 3) cout << "Major triad\n"; else if(y - x == 3 && z - y == 4) cout << "Minor triad\n"; else cout << "Dissonance\n"; } }
View Code

 

Problem B. Problem Select 00:06 (+) Solved by Guapi (排序 簽到)

代碼:

#include <bits/stdc++.h>
#define eps 1e-6
#define ls (rt << 1)
#define rs (rt << 1 | 1)
#define lowbit(x) (x & (-x))
#define SZ(v) ((int)(v).size())
#define All(v) (v).begin(), (v).end()
#define mp(x, y) make_pair(x, y)
#define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<int, int> P; const int N = 1e3 + 10; const int mod = 1e9 + 7; const int inf = 0x3f3f3f3f; int n, k, a[N]; char s[N]; int main() { int t; scanf("%d", &t); while(t --) { scanf("%d%d", &n, &k); for(int i = 1; i <= n; ++ i) { scanf("%s", s + 1); int len = strlen(s + 1); int j = len; while(j > 0 && s[j] != '/') -- j; int x = 0; for(j = j + 1; j <= len; ++ j) { x = x * 10 + s[j] -'0'; } a[i] = x; } sort(a + 1, a + n + 1); for(int i = 1; i <= k; ++ i) { printf("%d ", a[i]); } puts(""); } return 0; }
View Code

 

Problem C. String Game 00:32 (+1) Solved by Dancepted (dp 背包)

題目大意:

  給兩個字符串s,t,求字符串s有多少個子序列和t完全相同。

解法:

  用$cnt_{j}$表示t的長度為j的前綴能有多少個匹配。那么在后面出現$s_{i}=t_{j+1}$時,長度為j+1的前綴就會增加cnt_{j}個匹配。

  是一個類似01背包的轉移,所以第二個循環應該倒着跑。

代碼:$O(nm)$

#include <bits/stdc++.h>
#define sz(x) ((int)x.size())
#define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define endl '\n'
#define N 5005
#define M 1005

using namespace std; typedef long long ll; ll cnt[M]; ll md = 1000000007; int main(){ fast; string s, t; while (cin >> s >> t) { memset(cnt, 0, (sz(t) + 1) * sizeof(ll)); cnt[0] = 1; for (int i = 0; i < sz(s); i++) { for (int j = sz(t)-1; j >= 0; j--) { if (s[i] == t[j]) { cnt[j+1] = (cnt[j+1] + cnt[j]) % md; } } } cout << cnt[sz(t)] << endl; } return 0; }
View Code

 

Problem D. Trie (待補)

  hat的補題代碼(做法大概是AC自動機fail樹上用樹狀數組搞一下差分什么的)

#include<bits/stdc++.h>
#define forn(i, n) for (int i = 0; i < (n); i++)
#define forab(i, a, b) for (int i = (a); i <= (b); i++)
#define forba(i, b, a) for (int i = (b); i >= (a); i--)
#define mset(a, x, n) memset(a, x, sizeof(a[0]) * (n))
#define updiv(x, y) (((x) + (y) - 1) / (y)) // y > 0
#define pb(x) push_back(x)
#define eb emplace_back
#define mp(x, y) make_pair(x, y)
#define fi first
#define se second
#define sz(x) ((int)x.size())
#define all(x) (x).begin(), (x).end()
#define endl '\n' #ifdef hat #define fast
#define de(x) cout  << #x << '=' << (x) << ' '
#define dee(x) cout  << #x << '=' << (x) << endl
#else
#define fast ios::sync_with_stdio(0), cin.tie(0)
#define de(x) ((void) 0)
#define dee(x) ((void) 0)
#endif
using namespace std; typedef long long ll; typedef unsigned long long ull; typedef double db; typedef pair<int, int> pii; typedef vector<int> VI; const int maxn = 1e5 + 5; const int mod = 998244353; const int INF = 0x3f3f3f3f; const ll llINF = 0x3f3f3f3f3f3f3f3f; ll make_compiler_happy() {return INF & maxn & mod & llINF;} ll fpow(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;} ll inv(ll x) {return fpow(x, mod-2);} int ch[maxn][26]; int n; int f[maxn]; vector<int> g[maxn]; void getfail() { queue<int> q; f[0] = 0; for(int c = 0; c < 26; c++) { int u = ch[0][c]; if(u) { f[u] = 0; q.push(u); } } while(!q.empty()) { int r = q.front(); q.pop(); for(int c = 0; c < 26; c++) { int u = ch[r][c]; if(!u) continue; q.push(u); int v = f[r]; while(v && !ch[v][c]) v = f[v]; f[u] = ch[v][c]; } } for(int i = 1; i < n; i++) { g[f[i]].push_back(i); } } int cur; int in[maxn], out[maxn]; int dep[maxn]; void dfs1(int u, int d) { dep[u] = d; in[u] = ++cur; for(int v : g[u]) { dfs1(v, d + 1); } out[u] = cur; } int sum[maxn * 4]; #define mid (l+r)/2
#define ls o*2
#define rs o*2+1

void add(int o, int l, int r, int ql, int qr) { if(ql <= l && r <= qr) { sum[o]++; return; } if(ql > r || qr < l) { return; } add(ls, l, mid, ql, qr); add(rs, mid+1, r, ql, qr); } int bit[maxn]; void bitadd(int x, int d) { for(; x <= cur + 1; x += x & -x) { bit[x] += d; } } int bitsum(int x) { int s = 0; for(; x; x -= x & -x) { s += bit[x]; } return s; } void add(int x) { add(1, 1, cur, in[x], out[x]); } int query(int o, int l, int r, int x, int s) { if(l == r) return s + sum[o]; if(x <= mid) return query(ls, l, mid, x, s + sum[o]); else return query(rs, mid + 1, r, x, s + sum[o]); } int main() { fast; int T; cin >> T; while(T--) { cin >> n; memset(sum, 0, sizeof(sum)); for(int i = 0; i < n; i++) { g[i].clear(); memset(ch[i], 0, sizeof(ch[i])); } for(int i = 1; i < n; i++) { int u; char c; cin >> u >> c; u--; ch[u][c-'a'] = i; } cur = 0; getfail(); dfs1(0, 0); int m; cin >> m; while(m--) { int t; cin >> t; if(t == 1) { int k; cin >> k; vector<int> v(k); for(int i = 0; i < k; i++) { cin >> v[i]; v[i]--; } sort(v.begin(), v.end(), [&](int x, int y) {return dep[x] < dep[y];}); vector<int> tmp; for(int i : v) { if(bitsum(in[i])) { continue; } add(i); tmp.push_back(i); bitadd(in[i], 1); bitadd(out[i] + 1, -1); } for(int i : tmp) { bitadd(in[i], -1); bitadd(out[i] + 1, 1); } // forn(i, n) // { // cout << query(1, 1, cur, in[i], 0) << ' '; // } // cout << endl;
 } else { int x; cin >> x; x--; cout << query(1, 1, cur, in[x], 0) << endl; } } } }
View Code

 

Problem E. Shorten the Array 00:51 (+) Solved by Dancepted&hat (思維題)

  看題目發呆了好久,后來腦門一拍想了個結論就過了。

題目大意:

  給出一個長度為n(n <= 1e6)的數組,每次可以選擇兩個位置相鄰的數a、b(要求a和b都大於0),用a%b或者b%a替換他們。

思路:
  設最小的數是mn,數量有cnt個。

  如果cnt = 1,顯然可以通過(ai, mn)=(mn % ai)= (mn),來刪去所有的點,答案就是1。

  如果cnt ≠ 1,則需要討論一下。

兩個結論:

  1、若所有的數都是mn的倍數,則答案為$\left \lceil \frac{cnt}{2} \right \rceil$

  2、若存在一個數x不是mn的倍數,設y為與mn相鄰的一個數則必定可以通過(mn, y) = (mn % y),把mn換到x的旁邊,然后(mn, x)=(x%mn),且x%mn < mn,之后就可以用x%mn消去所有的

代碼:

#include <bits/stdc++.h>
#define eps 1e-6
#define ls (rt << 1)
#define rs (rt << 1 | 1)
#define lowbit(x) (x & (-x))
#define SZ(v) ((int)(v).size())
#define All(v) (v).begin(), (v).end()
#define mp(x, y) make_pair(x, y)
#define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define endl '\n'
using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<int, int> P; const int N = 1e6 + 10; const int mod = 1e9 + 7; const int inf = 0x3f3f3f3f; int n; int a[N]; void solveTestCase() { cin >> n; int mn = 1e9 + 5, cnt = 0; for (int i = 1; i <= n; i++) { cin >> a[i]; if (mn > a[i]) { mn = a[i]; cnt = 1; } else if (mn == a[i]) { cnt++; } } bool isOne = false; for (int i = 1; i <= n; i++) { if (a[i] % mn != 0) { isOne = true; break; } } if (isOne) cout << 1 << endl; else { cout << (cnt + 1) / 2 << endl; } } int main() { fast; int t; cin >> t; while (t--) { solveTestCase(); } return 0; }
View Code

 

Problem F. Queue 04:11 (+5) Solved by Guapi (樹狀數組+暴力,樹套樹)

  沒看到$p_{i2}−p_{i1} ≤ 100$,用樹套樹寫了半天沒調出來的樣子,后來看到了這個條件就暴力過了。

  因為太久做題,出現了一些RE,MLE之類的錯誤,多了不少罰時。

題目大意:

  動態逆序對,每次操作是交換兩個數的位置,或詢問逆序對的數量。

解法:

  樹套樹經典題,但這個兩個數的位置距離不超過100,也可以樹狀數組+暴力。

比賽代碼(樹狀數組+暴力):

#include <bits/stdc++.h>
using namespace std; #define lowbit(x) (x&(-x)) typedef long long ll; const int N = 1e5 + 10; const int M = 2500000; int a[N], n, m; int c[N]; void add(int x) { for(; x <= N - 5;  x += lowbit(x)) { c[x] ++ ; } } int ask(int x) { int res = 0; for(; x > 0; x -= lowbit(x)) { res += c[x]; } return res; } int main() { int T; scanf("%d", &T); while(T--) { ll cnt = 0; scanf("%d", &n); memset(c, 0, sizeof(c)); for(int i = 1; i <= n; ++ i) { scanf("%d", &a[i]); ++ a[i]; cnt += i - 1 - ask(a[i]); add(a[i]); } ll ans = cnt; scanf("%d", &m); int L, R; while(m --) { scanf("%d%d", &L, &R); if(L == R) continue; int cnt1 = 0, cnt2 = 0; for(int j = L + 1; j <= R - 1; ++ j) { if(a[j] < a[L]) ++ cnt1; if(a[j] > a[R]) ++ cnt2; } cnt = cnt - cnt1 - cnt2; cnt1 = cnt2 = 0; for(int j = L + 1; j <= R - 1; ++ j) { if(a[j] > a[L]) ++ cnt1; if(a[j] < a[R]) ++ cnt2; } cnt = cnt + cnt1 + cnt2; if(a[L] > a[R]) -- cnt; swap(a[L], a[R]); if(a[L] > a[R]) ++ cnt; ans = min(ans, cnt); } printf("%lld\n", ans); } return 0; }
View Code

樹套樹代碼:

#include <bits/stdc++.h>
using namespace std; #define lowbit(x) (x&(-x)) typedef long long ll; const int N = 1e5 + 10; const int M = 2500000; int a[N], tot, n, m, mx; struct tree{ int lc, rc, cnt; }T[N << 7]; int root[N];//正常樹的根節點
int ux[50], uy[50], numx, numy; //保存樹狀數組的節點
int S[N];//樹狀數組根節點
void build(int &rt, int l, int r)//建樹
{ rt = ++ tot; T[rt].cnt = 0; T[rt].lc = T[rt].rc = 0; if (l == r) return; int mid = (l + r) >> 1; build(T[rt].lc, l, mid); build(T[rt].rc, mid + 1, r); } void UP(int &cur, int l, int r, int pos, int val)//節省內存
{ if (!cur) cur = ++ tot; T[cur].cnt += val; if (l == r) return; int mid = (l + r) >> 1; if (pos <= mid) UP(T[cur].lc, l, mid, pos, val); else UP(T[cur].rc, mid + 1, r, pos, val); } void update(int &cur, int pre, int l, int r, int pos, int val) { cur = ++tot;//新樹必須新開節點
    T[cur] = T[pre]; T[cur].cnt += val; if (l == r) return; int mid = (l + r) >> 1; if (pos <= mid) update(T[cur].lc, T[pre].lc, l, mid, pos, val); else update(T[cur].rc, T[pre].rc, mid + 1, r, pos, val); } void clear(int &cur, int l, int r) { T[cur].cnt = 0; if(!cur || l == r) return ; int mid = (l + r) >> 1; if(T[cur].lc) clear(T[cur].lc, l, mid); if(T[cur].rc) clear(T[cur].rc, mid + 1, r); cur = 0; } void add(int pos, int val)//用樹狀數組更新
{ int x = a[pos]; for (; pos <= n; pos += lowbit(pos)) UP(S[pos], 0, mx, x, val); } int query(int l, int r, int L, int R, int x, int y) { if(x > y) return 0; if (x <= l && r <= y) { int ans = T[R].cnt - T[L].cnt; for (int i = 0; i < numy; i++) ans += T[uy[i]].cnt; for (int i = 0; i < numx; i++) ans -= T[ux[i]].cnt; return ans; } int  Ux[50], Uy[50]; for (int i = 0; i < numy; i++) Uy[i] = uy[i]; for (int i = 0; i < numx; i++) Ux[i] = ux[i]; int mid = (l + r) >> 1; int sum = 0; if (x <= mid) { for (int i = 0; i < numy; i++) uy[i] = T[Uy[i]].lc; for (int i = 0; i < numx; i++) ux[i] = T[Ux[i]].lc; sum = query(l, mid, T[L].lc, T[R].lc, x, y); } if (y > mid) { for (int i = 0; i < numy; i++) uy[i] = T[Uy[i]].rc; for (int i = 0; i < numx; i++) ux[i] = T[Ux[i]].rc; sum += query(mid + 1, r, T[L].rc, T[R].rc, x, y); } return sum; } int Q(int L) { int cnt = 0; // left, large than a[L]
    if(L > 1) { numx = 0, numy = 0; for (int j = 0; j > 0; j -= lowbit(j)) ux[numx++] = S[j]; for (int j = L - 1; j > 0; j -= lowbit(j)) uy[numy++] = S[j]; cnt += query(0, mx, root[0], root[L - 1], a[L] + 1, mx); } // right, less than a[L]
    if(L < n) { numx = 0, numy = 0; for (int j = L; j > 0; j -= lowbit(j)) ux[numx++] = S[j]; for (int j = n; j > 0; j -= lowbit(j)) uy[numy++] = S[j]; cnt += query(0, mx, root[L], root[n], 0, a[L] - 1); } return cnt; } int main() { int t; scanf("%d", &t); while(t --) { tot = 0; ll cnt = 0; scanf("%d", &n); mx = 0; for(int i = 1; i <= n; ++ i) { scanf("%d", &a[i]); mx = max(mx, a[i]); } build(root[0], 0, mx); numx = 0, numy = 0; for(int i = 1; i <= n; ++ i) { S[i] = 0; update(root[i], root[i - 1], 0, mx, a[i], 1); cnt += query(0, mx, root[0], root[i - 1], a[i] + 1, mx); } ll ans = cnt; int L, R; scanf("%d", &m); while(m --) { scanf("%d%d", &L, &R); if(L == R) continue; int delta = 0; delta -= Q(L) + Q(R); if(a[L] > a[R]) delta ++; add(L, -1); add(R, -1); swap(a[L], a[R]); add(L, 1); add(R, 1); delta += Q(L) + Q(R); if(a[L] > a[R]) delta --; cnt += delta; ans = min(ans, cnt); } printf("%lld\n", ans); for(int i = 1; i <= tot; ++ i) { T[i].cnt = T[i].lc = T[i].rc = 0; } } return 0; }
View Code

 

Problem G. Matrix 01:18 (+) Solved by Dancepted&hat (數論,思維題)

思路:

  翻轉次數為奇數的情況下,才對最終的答案有貢獻。

  (x,y)的翻轉次數 = x約數的個數 × y的約數的個數。

  設$x = \prod p_{i}^{t_{i}}(p_{i}是質數)$,則約數個數$d(x) = \prod(t_{i}+1)$,所有的$t_{i}$都必須是偶數,才對最終的答案有貢獻。

  也就是說x和y是完全平方數。$[1, n]$區間內的完全平方數的平方根的范圍為$[1, \left \lfloor \sqrt{n} \right \rfloor]$。

解法:

  答案就是$\left \lfloor \sqrt{n} \right \rfloor * \left \lfloor \sqrt{m} \right \rfloor$。

代碼:

#include <bits/stdc++.h>
#define eps 1e-6
#define ls (rt << 1)
#define rs (rt << 1 | 1)
#define lowbit(x) (x & (-x))
#define SZ(v) ((int)(v).size())
#define All(v) (v).begin(), (v).end()
#define mp(x, y) make_pair(x, y)
#define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define endl '\n'
using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<int, int> P; const int N = 1e6 + 10; const int mod = 1e9 + 7; const int inf = 0x3f3f3f3f; ll sq(ll x) { ll t = sqrt(x) - 1; if(t < 0) t = 0; while((t + 1) * (t + 1) <= x) t++; return t; } int main() { fast; int t; cin >> t; while (t--) { ll n, m; cin >> n >> m; cout << sq(n) * sq(m) << endl; } return 0; }
View Code

 

Problem H. Curious 03:43 (+1) Solved by Dancepted&hat (數論 容斥 莫比烏斯函數)

需要使勁分析復雜度的一道數論題。

WA了一發是因為加記憶化的時候,忘記用long long了。

題目大意:

  給出一個長度為n的數列a,滿足$a_{i}<=m$,k次詢問,每次給出一個x,回答:

  $\sum_{i=1}^{n}\sum_{j=1}^{n}[gcd(a_{i}, a_{j})==x]$

思路:

  用$sum_{i}$表示數列中為i的倍數的數的數量。

  ①對於x = 1的情況:

  $\sum_{i=1}^{n}\sum_{j=1}^{n}[gcd(a_{i}, a_{j})==1]$

  所有1的倍數的gcd至少為1,這些數的數量就是$sum_{1}*sum_{1}$,但這里面要減去gcd為2、3、5...的那些數,但這樣6、10、15之類的又被多減了。

  實際上$sum_{i}*sum_{i}$的系數正好是莫比烏斯函數$\mu(i)$。

  因此對於x = 1,有$ans = \sum_{m}^{i=1}\mu (i)sum_{i}^2$。

  預處理sum_{i}時,可以先O(n)地統計[1, m]內每個數的出現次數,然后用埃氏篩在O(mloglogm)的時間內計算sum。

  總的時間復雜度是O(n+mloglogm)的。

  ②同樣的,對於一個給定的x,有:

  $\sum_{i=1}^{n}\sum_{j=1}^{n}[gcd(a_{i}, a_{j})==x] = \sum_{i=1}^{n}\sum_{j=1}^{n}[gcd(\frac{a_{i}}{x}, \frac{a_{j}}{x})==1]$。

  把數列中所有x的倍數的數單獨拿出來,並除x。再跑一遍①中的算法,就可以得到答案了,令m' = m / x,時間復雜度為$O(n + m'loglogm)。

  這樣看起來總復雜度似乎高達$O(n*n + n*mloglogm))$。

  復雜度右邊的部分實際上是$\sum^{m}_{x=1}\frac{m}{x}loglog(m/x) <=mlogm\sum^{m}_{x=1}\frac{1}{x}$,而$\frac{1}{x}$的上界是一個常數(百度了一下好像是$\frac{\pi^2}{6}$)

  並且,數列中的每個數,只會被①調用$d(a_{i})$次($d(n)$表示n的約數的個數),而d(n)的一個顯然的上界是$2\sqrt{n}$,復雜度的左邊實際上是$O(n\sqrt{n})$。

  對於重復的x,也可以記憶化。

代碼:$O(n\sqrt{n}+mloglogm))$

#include <bits/stdc++.h>
#define eps 1e-6
#define ls (rt << 1)
#define rs (rt << 1 | 1)
#define lowbit(x) (x & (-x))
#define SZ(v) ((int)(v).size())
#define All(v) (v).begin(), (v).end()
#define mp(x, y) make_pair(x, y)
#define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define endl '\n'
using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<int, int> P; const int N = 1e5 + 10; const int mod = 1e9 + 7; const int inf = 0x3f3f3f3f; int a[N]; vector<int> V[N]; int prime[N], miu[N]; int cnt[N], sum[N]; ll solve(vector<int> &v) { int n = SZ(v); int m = 0; for(int i = 0; i < n; i++) { m = max(m, v[i]); } memset(cnt, 0, sizeof(int) * (m + 1)); for(int i = 0; i < n; i++) cnt[v[i]]++; memset(sum, 0, sizeof(int) * (m + 1)); // ll ans = (ll) n * n;
    ll ans = 0; for(int i = 1; i <= m; i++) { for(int j = i; j <= m; j += i) { sum[i] += cnt[j]; } ans += (ll) miu[i] * sum[i] * sum[i]; } return ans; } void solveTestCase() { int n, m, k; cin >> n >> m >> k; for(int i = 1; i <= m; i++) { V[i].clear(); cnt[i] = 0; } for(int i = 1; i <= n; i++) { cin >> a[i]; cnt[a[i]]++; } for(int i = 1; i <= m; i++) { for(int j = i; j <= m; j+=i) { for(int _ = 0; _ < cnt[j]; _++) { V[i].push_back(j / i); } } } map<int, ll> mp; for(int i = 0; i < k; i++) { int x; cin >> x; if(x > m || x <= 0) cout << 0 << endl; else { if(mp.count(x)) { cout << mp[x] << endl; } else { ll t = solve(V[x]); mp[x] = t; cout << t << endl; } } } } void getPrime_and_Miu() { miu[1] = 1; for (int i = 2; i <= N; i++) { if (!prime[i]) prime[++prime[0]] = i, miu[i] = -1; for (int j = 1; j <= prime[0] && prime[j] <= N/i; j++) { prime[i*prime[j]] = 1; miu[i*prime[j]] = miu[i] * (i%prime[j] ? -1 : 0); if (i%prime[j] == 0) break; } } } int main() { fast; getPrime_and_Miu(); int t; cin >> t; while (t--) { solveTestCase(); } return 0; }
View Code

 

Problem I. World Tree 04:05 (+) Solved by Dancepted (經典貪心 + 樹形dp)

思路:

  題目中有說到,如果當前節點存在孩子,就不能返回到父節點,換句話說就是在樹上dfs。

  所以如果有策略可以確定dfs的順序,那么這題就解決了。

 

  令$A_{u} = \sum_{v\in以u為根的子樹}a_{v}$,$B_{u} = \sum_{v\in以u為根的子樹}b_{v}$

  以節點$u$為根的子樹,對答案的貢獻可以分成兩類:

  1、子樹的根$u$與子樹內所有其他節點之間產生的貢獻,這部分是無法改變的,為$a_{u}*(B_{u} - b_{u})$;

  2、以$u$為根的子樹,與以$u$的兄弟節點$u'$及其子樹產生的貢獻,有兩種可能的情況:

    ①$A_{u}B_{u'}$,此時是先遍歷u子樹,再遍歷u'子樹;

    ②$A_{u'}B_{u}$,此時是先遍歷u'子樹,再遍歷u子樹;

  如果①>②,說明先遍歷u子樹更優,反之亦然。

  在dfs到下一層之前,根據這個規則對子節點排序后再遍歷就可以了。

  這個貪心策略之前在CF上做過類似的題

代碼:O(nlogn)

#include <bits/stdc++.h>
#define eps 1e-6
#define ls (rt << 1)
#define rs (rt << 1 | 1)
#define lowbit(x) (x & (-x))
#define SZ(v) ((int)(v).size())
#define All(v) (v).begin(), (v).end()
#define mp(x, y) make_pair(x, y)
#define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define endl '\n'
using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<int, int> P; const int N = 1e5 + 10; const int mod = 1e9 + 7; const int inf = 0x3f3f3f3f; int n; int a[N], b[N]; ll sa[N], sb[N]; vector<int> es[N]; ll ans = 0; void dfsInit(int u, int fa) { sa[u] = a[u]; sb[u] = b[u]; for (int v : es[u]) if (v != fa) { dfsInit(v, u); sa[u] += sa[v]; sb[u] += sb[v]; } } bool cmp(int l, int r) { return sa[l]*sb[r] > sa[r]*sb[l]; } void dfsSolve(int u, int fa) { ans += (ll)a[u]*(sb[u] - b[u]); sort(es[u].begin(), es[u].end(), cmp); ll tmp = sb[u] - b[u]; for (int v : es[u]) if (v != fa) { dfsSolve(v, u); tmp -= sb[v]; ans += sa[v] * tmp; } } void solveTestCase() { cin >> n; for (int i = 1; i <= n; i++) es[i].clear(); for (int i = 1; i <= n; i++) cin >> b[i]; for (int i = 1; i <= n; i++) cin >> a[i]; for (int i = 1; i <= n-1; i++) { int u, v; cin >> u >> v; es[u].push_back(v); es[v].push_back(u); } dfsInit(1, -1); ans = 0; dfsSolve(1, -1); cout << ans << endl; } int main() { fast; int t; cin >> t; while (t--) { solveTestCase(); } return 0; }
View Code

 

Problem J. Situation 01:55 (+1) Solved by hat(min-max)

少開了一個狀態,WA了一發。

思路:

  總狀態數是$3^{9}*2\approx 4*10^{4}$。可以dfs加剪枝,dfs中用min-max決策。

代碼:

#include <bits/stdc++.h>
#define eps 1e-6
#define ls (rt << 1)
#define rs (rt << 1 | 1)
#define lowbit(x) (x & (-x))
#define SZ(v) ((int)(v).size())
#define All(v) (v).begin(), (v).end()
#define mp(x, y) make_pair(x, y)
#define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define endl '\n'
using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<int, int> P; const int N = 1e3 + 10; const int mod = 1e9 + 7; const int inf = 0x3f3f3f3f; struct state{ int a[3][3]; int toint() { int p3 = 1; int res = 0; for(int i = 0; i < 3; i++) { for(int j = 0; j < 3; j++) { res += a[i][j] * p3; p3 *= 3; } } return res; } bool isend() { for(int i = 0; i < 3; i++) { for(int j = 0; j < 3; j++) { if(!a[i][j]) return 0; } } return 1; } int cal() { int t = 0; for(int i = 0; i < 3; i++) { int f = a[i][0]; for(int j = 1; j < 3; j++) { if(a[i][j] != f) f = 0; } if(f) { if(f == 1) t++; else t--; } } for(int i = 0; i < 3; i++) { int f = a[0][i]; for(int j = 1; j < 3; j++) { if(a[j][i] != f) f = 0; } if(f) { if(f == 1) t++; else t--; } } int f = a[0][0]; for(int j = 1; j < 3; j++) { if(a[j][j] != f) f = 0; } if(f) { if(f == 1) t++; else t--; } f = a[0][2]; for(int j = 1; j < 3; j++) { if(a[j][2-j] != f) f = 0; } if(f) { if(f == 1) t++; else t--; } return t; } }; int res[30000][2]; int dfs(state s, int ismax) { int x = s.toint(); if(res[x][ismax] != -1000) { return res[x][ismax]; } if(s.isend()) { return res[x][ismax] = s.cal(); } if(ismax) { // O:1
        for(int i = 0; i < 3; i++) for(int j = 0; j < 3; j++) if(s.a[i][j] == 0) { state t = s; t.a[i][j] = 1; res[x][ismax] = max(dfs(t, !ismax), res[x][ismax]); } } else { // X:2
        res[x][ismax] = 1000; for(int i = 0; i < 3; i++) for(int j = 0; j < 3; j++) if(s.a[i][j] == 0) { state t = s; t.a[i][j] = 2; res[x][ismax] = min(dfs(t, !ismax), res[x][ismax]); } } return res[x][ismax]; } int main() { for(int i = 0; i < 30000; i++) res[i][0] = res[i][1] = -1000; int t; cin >> t; while (t--) { int ismax; cin >> ismax; state t; for(int i = 0; i < 3; i++) { string s; cin >> s; for(int j = 0; j < 3; j++){ if(s[j] == '.') t.a[i][j] = 0; else if(s[j] == 'O') t.a[i][j] = 1; else t.a[i][j] = 2; } } cout << dfs(t, ismax) << endl; } return 0; }
View Code

 

Problem L. Swimmer 00:30 (+1) Solved by Guapi (簽到)

RE了一發是因為數組開小了。。。

代碼:

#include <bits/stdc++.h>
#define eps 1e-6
#define ls (rt << 1)
#define rs (rt << 1 | 1)
#define lowbit(x) (x & (-x))
#define SZ(v) ((int)(v).size())
#define All(v) (v).begin(), (v).end()
#define mp(x, y) make_pair(x, y)
#define fast ios::sync_with_stdio(0), cin.tie(0), cout.tie(0)
#define endl '\n'
using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<int, int> P; const int N = 1e6 + 10; const int mod = 1e9 + 7; const int inf = 0x3f3f3f3f; int n, m, q, a[N]; int main() { scanf("%d%d%d", &n, &m, &q); for(int i = 1; i <= n; ++ i) { scanf("%d", &a[i]); } int p, k; for(int i = 1; i <= q; ++ i) { scanf("%d%d", &p, &k); int y = (1LL * p * a[k] ) % (2 * m); if(y <= m) printf("%d\n", y); else printf("%d\n", 2 * m - y); } return 0; }
View Code

 

Problem M. Upanishad 04:38 (+) Solved by Guapi (樹狀數組)

知道是熱身賽的題,就沒急着做。

思路:

  區間出現次數為偶數的異或和 = 區間出現過的數的異或和 xor 區間出現次數為奇數的異或和。

  出現次數為奇數的異或和比較簡單,用異或和的前綴和算一算就可以了。(出現次數為偶數的異或和為0)

  區間出現過的數,可以這么求(好像是樹狀數組的一個trick?):遍歷的時候如果一個數之前出現過,那么就把樹狀數組中,在之前出現的位置減去這個數,再在新的位置加上這個數。

代碼:O(nlogn)

#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x&(-x))
typedef long long ll;
const int N = 5e5 + 10;
const int M = 2500000;
int a[N], sum[N], n, q, ans[N];
int c[N];
map<int, int> mp;
struct node {
    int l, r, pos;
    bool operator<(const node &o)const {
        return r < o.r;
    }
};
node Q[N];
void add(int x, int val) {
    for(; x <= n;  x += lowbit(x)) {
        c[x] ^= val ;
    }
}
int ask(int x) {
    int res = 0;
    for(; x > 0; x -= lowbit(x)) {
        res ^= c[x];
    }
    return res;
}
int main()
{
    scanf("%d%d", &n, &q);
    for(int i = 1; i <= n; ++ i) {
        scanf("%d", &a[i]);
        sum[i] = a[i] ^ sum[i - 1];
    }
    for(int i = 1; i <= q; ++ i) {
        scanf("%d%d", &Q[i].l, &Q[i].r);
        Q[i].pos = i;
    }
    sort(Q + 1, Q + q + 1);
    int L = 1;
    for(int i = 1; i <= q; ++ i) {
        while(L <= Q[i].r) {
            if(mp.count(a[L])){
                add(mp[a[L]], a[L]);
            }
            mp[a[L]] = L;
            add(L, a[L]);
            ++ L;
        }
        int res = sum[Q[i].r] ^ sum[Q[i].l - 1];
        int res1 = ask(Q[i].r) ^ ask(Q[i].l - 1);
        res ^= res1;
        ans[Q[i].pos] = res;
    }
    for(int i = 1; i <= q; ++ i) {
        printf("%d\n", ans[i]);
    }
    return 0;
}
View Code

 


 

  

總結:

  省賽還是偏輕松愉快的2333,不過剛回來還是會犯一些低級錯誤,還有個trick因為太久沒做題了,WA了一發才想起來。

  C(+1)的解法實現起來的時候,如果正向轉移的話,同一層會自己影響自己,這是背包問題里比較常見的trick了,模擬賽中寫的時候沒有意識到這個問題。

  F(+5)暴露出的問題是讀題不夠仔細+對板子不過熟悉。首先是沒有讀到完整的題意,實際上做題的時候,如果有特別地說明區間跨度不超過100之類的,肯定是會針對這個條件來設計解法的。其次是題意讀了個大概,然后發現和樹套樹的板子很像,就拋開題目直接上板子了。雖然實際上樹套樹確實可做,但因為數據量比較特殊,有更簡單的解法,可以省下不少機時。

  G(+)雖然是一發過的,但是思路上繞了個大彎。我們隊對各種數論篩法還是不夠熟悉,上來有點念念不忘數論篩法,誤以為這是個篩子題,想着怎么用篩子做。過了很久才發現這就是個結論題。

  H(+1)代碼寫的沒問題,交之前順手加了個記憶化,結果記憶化的部分沒有用long long。

  J(+1)如果是我寫的話,我可能也會犯這樣的錯誤吧。動手寫中期題的時候,還是要把思路想清楚再摸鍵盤。邊寫邊想還是很容易漏掉一些點的。

  L(+1)數組開太小了。。

  還有就是我們討論的時候容易想到什么就直接說,但我覺得這樣討論的效率其實是比較低的。我覺得應該先獨立思考,有什么關鍵的進展,或者想到了什么解法,再和隊友討論。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM