模拟赛小结: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