CF1417 题解
下午打 div3 成绩十分可怜,晚上打 div2 虚拟赛居然能 AK
CF1417A
Description
给定一个长为 $n$ 的数列 $a$,每次可以指定 $i,j$ ,令 $a_j=a_i+a_j$,求使得在所有数都不超过 $k$ 的情况下最多的操作次数。
$n\le 1000,k\le10^4$
Sol
贪心,最小的数不动,每次都给每个数加上最小的数,直到不能再加为止。
时间复杂度 $\mathcal{O}(n)$。
Code
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> using namespace std; int Read() { int x = 0, f = 1; char ch = getchar(); while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();} while(isdigit(ch)) {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();} return x * f; } int a[10005]; signed main() { int T = Read(); while(T--) { int n = Read(), k = Read(), minn = 1E9, mpos; for(int i = 1; i <= n; i++) { a[i] = Read(); if(a[i] < minn) minn = a[i], mpos = i; } int ans = 0; for(int i = 1; i <= n; i++) { if(i != mpos) ans += (k - a[i]) / minn; } cout << ans << endl; } return 0; }
CF1417B
Description
给定一个长为 $n$ 的数列 $a$ ,给定 $k$,定义一个数列的不完美度 $f(S)$ 为 $\sum_{i=1}^n\sum_{j=1}^n [S_i+S_j=k]$,将给定数列中的数划分为两个数列 $A,B$,求 $f(A)+f(B)$ 最小时的划分方案。
$n\le 10^5, k\le10^9$
Sol
当 $k$ 为奇数时,我们直接把 $\le \frac k2$ 的数划分为一堆,$\ge \frac k2$ 的数划分为一堆,这样第一堆任意两数的和必定 $\le k$,第二堆任意两数之和必定 $\ge k$。
当 $k$ 为偶数时,我们单独把 $=\frac k2$ 的数提出来,记录这些数的数目,其他数按照上述方式划分,最后将提出来的那些数平分给 $A,B$ 即可。
时间复杂度 $\mathcal{O} (n)$。
Code
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> using namespace std; int Read() { int x = 0, f = 1; char ch = getchar(); while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();} while(isdigit(ch)) {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();} return x * f; } int T, n, k, a[100005], b[100005]; signed main() { T = Read(); while(T--) { memset(b, -1, sizeof(b)); n = Read(), k = Read(); int sum = 0, flag = 0; for(int i = 1; i <= n; i++) a[i] = Read(); for(int i = 1; i <= n; i++) { if(a[i] < k / 2) b[i] = 1; if(a[i] > k / 2) b[i] = 0; if(a[i] == k / 2 && k % 2 == 0) ++sum; if(a[i] == k / 2 && k % 2 == 1) b[i] = 1; } if(k % 2 == 0) { for(int i = 1; i <= n; i++) { if(flag < sum / 2 && b[i] == -1) b[i] = 0, ++flag; if(flag >= sum / 2 && b[i] == -1) b[i] = 1; } } for(int i = 1; i <= n; i++) cout << b[i] << " "; puts(""); } return 0; }
CF1417C
Description
给定一个长为 $n$ 的数列 $a$ ,请对于每个 $k\in[1,n]$ 求出在区间 $[1,k],[2,k+1],...[n-k+1,n]$ 中均出现的最小数。
$n\le 3\times 10^5$
Sol
考虑一个数在每个区间中均出现的情况,设其在数列中出现的位置为 $pos_1,pos_2,...,pos_m$,当且仅当 $\forall i>1,pos_i-pos_{i-1}\le k$,且$pos_1 \le k,n - pos_m\le k $。所以我们可以先对于每个数处理其最小能在哪个区间中出现。
然后用上述记录的东西来推出每个区间能够放置的最小的数,注意到当某个数满足一个 $k$ 时,那么所有大于 $k$ 的区间其均可以满足。
更新后输出即可。
时间复杂度 $\mathcal{O}(n)$。
Code
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> using namespace std; int Read() { int x = 0, f = 1; char ch = getchar(); while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();} while(isdigit(ch)) {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();} return x * f; } int T, n, a[300005], ldis[300005], mdis[300005], ans[300005]; signed main() { T = Read(); while(T--) { memset(ldis, 0, sizeof(ldis)); memset(mdis, 0, sizeof(mdis)); memset(ans, 10, sizeof(ans)); int n = Read(); for(int i = 1; i <= n; i++) a[i] = Read(); for(int i = 1; i <= n; i++) mdis[a[i]] = max(mdis[a[i]], i - ldis[a[i]]), ldis[a[i]] = i; for(int i = 1; i <= n; i++) mdis[a[i]] = max(mdis[a[i]], n + 1 - ldis[a[i]]); for(int i = 1; i <= n; i++) ans[mdis[a[i]]] = min(ans[mdis[a[i]]], a[i]); for(int i = 2; i <= n; i++) ans[i] = min(ans[i], ans[i - 1]); for(int i = 1; i <= n; i++) { if(ans[i] > n) printf("%d ", -1); else printf("%d ", ans[i]); } puts(""); } return 0; }
CF1417D
Description
给定一个长度为 $n$ 的数列 $a$,你可以进行下列操作:
- 选择 $3$ 个数 $i,j,x$,使 $a_i=a_i-i\times x,a_j=a_j+i\times x$,$a_i,j$需要保持非负。
你需要在至多 $3n$ 次操作内将数列中所有数变为同一个数,问一个可行方案。
$n\le 10^4,1\le a_i\le 10^5$
Sol
首先当数列的和不为 $n$ 的倍数时,肯定是无解的。
我们考虑当数列的和为 $n$ 的倍数时,每个数需要变成 $\frac{sum}{n}$,那么我们考虑一种构造方式:
由于选 $i$ 为 $1$ 时可以方便的转移,我们考虑将每个位置上的权值都移到位置 $1$,那么我们可以先用 $1$ 里的数将该位置上的数补至 $i$ 的倍数,然后再转移至 $1$,这样需要至多 $2n$ 次操作,然后从 $1$ 移出去需要至多 $n$ 次操作。
时间复杂度 $\mathcal{O}(n)$。
Code
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> #define int long long using namespace std; int Read() { int x = 0, f = 1; char ch = getchar(); while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();} while(isdigit(ch)) {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();} return x * f; } int T, n, sum, a[100005], A[100005], B[100005], C[100005], cnt; signed main() { T = Read(); while(T--) { n = Read(); cnt = 0; sum = 0; for(int i = 1; i <= n; i++) a[i] = Read(), sum += a[i]; if(sum % n) {puts("-1"); continue;} for(int i = 2; i <= n; i++) { if(a[i] % i == 0) A[++cnt] = i, B[cnt] = 1, C[cnt] = a[i] / i; else { A[++cnt] = 1, B[cnt] = i, C[cnt] = i - (a[i] % i); a[i] += i - (a[i] % i); A[++cnt] = i, B[cnt] = 1, C[cnt] = a[i] / i; } } int tmp = sum / n; for(int i = 2; i <= n; i++) { A[++cnt] = 1, B[cnt] = i, C[cnt] = tmp; } printf("%lld\n", cnt); for(int i = 1; i <= cnt; i++) { printf("%lld %lld %lld\n", A[i], B[i], C[i]); } } return 0; }
CF1417E
Description
给定一个长度为 $n$ 的数列 $a$,要求让你选取一个 $x$,求出数列 $b$,其中 $b_i=a_i \bigoplus x$,$\bigoplus$ 即异或,使得在数列 $b$ 的逆序对数最小时 $x$ 的最小值。
$n\le 3\times 10^5,a_i\le 10^9$
Sol
由于有异或的出现,容易想到需要按位分析,我们从高到低枚举,考虑枚举到最高的有数字的该位为 $1$ 的数 $i$,那么如果我们该位已经定了,那么所有的数一定会分为两部分,一部分是大于等于 $2^i$ 的数,一部分是小于 $2^i$ 的数,那么由于是按位分析,那么这两部分间数的大小关系是已经定下来了的,已经产生的逆序对数也不会减少,这意味着我们已经算出了这两部分间的贡献,那么单独我们就可以把这些数分为两部分,对于每一个部分继续枚举位数,算出它们的贡献。
时间复杂度 $\mathcal{O}(n\log 10^9)$
Code
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> #define int long long using namespace std; int Read() { int x = 0, f = 1; char ch = getchar(); while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();} while(isdigit(ch)) {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();} return x * f; } int n, a[300005], f[35][2], cnt; struct qj { vector<int> num; vector<int> hdif; }s[300005]; signed main() { n = Read(); for(int i = 1; i <= n; i++) s[1].num.push_back(Read()); ++cnt; for(int k = 30; k >= 0; k--) { for(int i = 1; i <= cnt; i++) { int sum1 = 0, sum2 = 0; s[i].hdif.resize(s[i].num.size()); for(int j = s[i].num.size() - 1; j >= 0; j--) { if((s[i].num[j] & (1 << k)) != 0) s[i].hdif[j] = sum2, ++sum1; else s[i].hdif[j] = sum1, ++sum2; } for(int j = 0; j < s[i].num.size(); j++) { if((s[i].num[j] & (1 << k)) != 0) f[k][0] += s[i].hdif[j]; else f[k][1] += s[i].hdif[j]; } } for(int i = 1; i <= cnt; i++) { int sum1 = 0, sum2 = 0; for(int j = 0; j < s[i].num.size(); j++) if((s[i].num[j] & (1 << k)) != 0) ++sum2; else ++sum1; if(!sum1 || !sum2) continue; ++cnt; vector<int> A; for(int j = 0; j < s[i].num.size(); j++) { if((s[i].num[j] & (1 << k)) != 0) s[cnt].num.push_back(s[i].num[j]); else A.push_back(s[i].num[j]); } s[i].num = A; } } int res = 0, sum = 0; for(int i = 0; i <= 30; i++) { if(f[i][0] <= f[i][1]) sum += f[i][0]; else res += (1 << i), sum += f[i][1]; } cout << sum << " " << res << endl; return 0; }
CF1417F
Description
给定一张图,有 $n$ 个点,每个点有点权 $a_i$,$m$ 条边,要求支持 $q$ 个操作,操作分两种:
- 给定点 $x$ ,令 $x$ 能够到达的点中点权最大的点为 $u$ ,输出 $a_u$ ,并将 $a_u$ 更新为 $0$。
- 删掉第 $i$ 条边。
$n\le 2\times 10^5,m\le 3\times 10^5,q\le 5\times10^5$
Sol
$1$ 操作需要正着来, $2$ 操作需要倒着来,粗略一看不好处理。
考虑树链剖分的思想,我们把一条重链上的点在线段树上的编号变为连续的编号,那么我们考虑对于每一个询问 $1$,我们需要将它能够走到的点的编号变为连续的。
我们先将所有操作存下来,倒着加边,用并查集维护,先建出图的一棵生成树,考虑维护编号的连续性,我们将每个点的编号初始化为 $1$,当合并时打一个加法 tag ,代表这个集合的点的编号都要加上一个值,最后从根 dfs 扫一遍就可以得出每个点的编号,注意图可能不连续。
然后清空并查集,按照相同的方式维护,处理出每个点集最小的编号与最大的编号,对于每个询问 $1$ ,它能够到达的点的区间就是该点集编号最小的点到编号最大的点,且它们的编号连续,最后正着拿线段树维护即可。
时间复杂度 $\mathcal{O}(n\log n)$
Code
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> using namespace std; int Read() { int x = 0, f = 1; char ch = getchar(); while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();} while(isdigit(ch)) {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();} return x * f; } int first[800005], nxt[800005], to[800005], tot = 0; void Add(int x, int y) { nxt[++tot] = first[x]; first[x] = tot; to[tot] = y; } int ex[500005], ey[500005], vis[500005], rt[500005], tag[500005]; int sz[500005], bh[500005], a[500005], opt[500005], x[500005], vvis[500005], maxn[500005], minn[500005]; int L[500005], R[500005], rev[500005], mx[2000005], Pos[2000005]; void pushup(int o) { if(mx[o << 1] > mx[o << 1 | 1]) mx[o] = mx[o << 1], Pos[o] = Pos[o << 1]; else mx[o] = mx[o << 1 | 1], Pos[o] = Pos[o << 1 | 1]; } void build(int o, int l, int r) { if(l == r) {mx[o] = a[rev[l]]; Pos[o] = l; return ;} int mid = (l + r) >> 1; build(o << 1, l, mid); build(o << 1 | 1, mid + 1, r); pushup(o); } void modify(int o, int l, int r, int pos, int val) { if(l == r) {mx[o] = val; return ;} int mid = (l + r) >> 1; if(pos <= mid) modify(o << 1, l, mid, pos, val); else modify(o << 1 | 1, mid + 1, r, pos, val); pushup(o); } pair<int, int> my_max(pair<int, int> A, pair<int, int> B) { return (A.first > B.first) ? A : B; } pair<int, int> query(int o, int l, int r, int nl, int nr) { if(nl <= l && r <= nr) return make_pair(mx[o], Pos[o]); int mid = (l + r) >> 1; pair<int, int> ans; ans.first = ans.second = 0; if(nl <= mid) ans = my_max(ans, query(o << 1, l, mid, nl, nr)); if(mid < nr) ans = my_max(ans, query(o << 1 | 1, mid + 1, r, nl, nr)); return ans; } int findfa(int x) {return rt[x] == x ? x : rt[x] = findfa(rt[x]);} void dfs(int u) { vvis[u] = 1; bh[u] += tag[u]; for(int e = first[u]; e; e = nxt[e]) { int v = to[e]; tag[v] += tag[u]; dfs(v); } } signed main() { int n = Read(), m = Read(), q = Read(); for(int i = 1; i <= n; i++) sz[i] = bh[i] = 1, rt[i] = i, a[i] = Read(); for(int i = 1; i <= m; i++) ex[i] = Read(), ey[i] = Read(); for(int i = 1; i <= q; i++) { opt[i] = Read(), x[i] = Read(); if(opt[i] == 2) vis[x[i]] = 1; } for(int i = 1; i <= m; i++) { if(!vis[i]) { int fx = findfa(ex[i]), fy = findfa(ey[i]); if(fx == fy) continue; tag[fx] += sz[fy]; sz[fy] += sz[fx]; rt[fx] = fy; Add(fy, fx); } } for(int i = q; i >= 1; i--) { if(opt[i] == 2) { int fx = findfa(ex[x[i]]), fy = findfa(ey[x[i]]); if(fx == fy) continue; tag[fx] += sz[fy]; sz[fy] += sz[fx]; rt[fx] = fy; Add(fy, fx); } } int sum = 0; for(int i = 1; i <= n; i++) if(!vvis[findfa(i)]) tag[rt[i]] += sum, sum += sz[rt[i]], dfs(rt[i]); for(int i = 1; i <= n; i++) maxn[i] = minn[i] = bh[i], rt[i] = i, rev[bh[i]] = i; for(int i = 1; i <= m; i++) { if(!vis[i]) { int fx = findfa(ex[i]), fy = findfa(ey[i]); if(fx == fy) continue; maxn[fy] = max(maxn[fy], maxn[fx]); minn[fy] = min(minn[fy], minn[fx]); rt[fx] = fy; } } for(int i = q; i >= 1; i--) { if(opt[i] == 2) { int fx = findfa(ex[x[i]]), fy = findfa(ey[x[i]]); if(fx == fy) continue; maxn[fy] = max(maxn[fy], maxn[fx]); minn[fy] = min(minn[fy], minn[fx]); rt[fx] = fy; } if(opt[i] == 1) L[i] = minn[findfa(x[i])], R[i] = maxn[findfa(x[i])]; } build(1, 1, n); for(int i = 1; i <= q; i++) { if(opt[i] == 1) { pair<int, int> tmp = query(1, 1, n, L[i], R[i]); printf("%d\n", tmp.first); modify(1, 1, n, tmp.second, 0); } } return 0; }