2022“杭电杯”中国大学生算法设计超级联赛(6) 题解
F. Maex
设 \(dp[u]\) 表示 \(\sum_{v在u的子树内} b_v\) 的最大值,\(sze[u]\) 表示 \(u\) 子树的大小。
有 \(dp[u] = \max_{v为u的儿子}(dp[v]) + sze[u]\)
在 \(u\) 的子树中填充数字 \(0\) ~ \(sze[u]-1\),\(b[u]\) 的值就为 \(sze[u]\)。
因为 \(0\) 只能出现在 \(u\) 的其中一个子树内,而其他子树的sum值此时一定都为 \(0\),所以 \(\max_{v为u的儿子}(dp[v])\) 为子树传递上来的答案。
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 5e5 + 5;
int n, dp[MAXN], sze[MAXN];
vector<int> G[MAXN];
inline void read(int &X) {
X = 0; int w = 0; char ch = 0;
while(!isdigit(ch)) w |= ch=='-', ch = getchar();
while( isdigit(ch)) X = (X<<3)+ (X<<1) + (ch-48), ch = getchar();
X = w ? -X : X;
}
inline void write(int x) {
static int sta[65];
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while(x);
while(top) putchar(sta[--top] + 48);
}
void DFS(int u, int f) {
dp[u] = 0, sze[u] = 1;
for(auto v : G[u]) {
if(v == f) continue;
DFS(v, u);
sze[u] += sze[v];
}
dp[u] = sze[u];
int maxx = 0;
for(auto v : G[u]) {
if(v == f) continue;
maxx = max(maxx, dp[v]);
}
dp[u] += maxx;
}
void Solve() {
read(n);
for(int i = 1; i <= n; i++) G[i].clear(), dp[i] = 0;
for(int i = 1; i < n; i++){
int u, v; read(u); read(v);
G[u].push_back(v);
G[v].push_back(u);
}
DFS(1, 0);
write(dp[1]); putchar('\n');
}
signed main()
{
//ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; read(T);
while(T--) Solve();
return 0;
}
G. Shinobu loves trip
首先预处理出所有 \(a^k , 0 \leq k \leq 200000\),求出 \(a^k \% p = Y\) 的最小 \(k\) 。
对于每次询问 \(x\),我们枚举每个旅行计划 \(s, d\),此时需要找到 \(s \cdot a^k \equiv x (mod \ P)\) 的最小 \(k\),并与 \(d\) 做比较。
将式子转化为 \(a^k \equiv \frac{x}{s} (mod \ P)\),利用费马小定理将除法转成乘法逆元即可,即 \(a^k \equiv x \cdot s^{P-2} (mod \ P)\)。
因为离散化时没加入0,所以需要注意特判0的情况。
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define fi first
#define se second
#define pii pair<int, int>
#define int long long
using namespace std;
const int MAXN = 4e5 + 5;
const int INF = 1e9 + 10;
int n, q, P, a, b, len, inv[MAXN], s[MAXN], d[MAXN];
int vec[MAXN], num[MAXN];
inline void read(int &X) {
X = 0; int w = 0; char ch = 0;
while(!isdigit(ch)) w |= ch=='-', ch = getchar();
while( isdigit(ch)) X = (X<<3)+ (X<<1) + (ch-48), ch = getchar();
X = w ? -X : X;
}
inline void write(int x) {
static int sta[65];
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while(x);
while(top) putchar(sta[--top] + 48);
}
int qpow(int a, int p) {
int res = 1;
while(p) {
if(p & 1) res = res * a % P;
a = a * a % P;
p >>= 1;
}
return res;
}
int Find(int x) {
int pos = lower_bound(vec + 1, vec + 1 + len, x) - vec;
if(vec[pos] != x) return -INF;
return pos;
}
void Solve() {
cin >> P >> a >> n >> q;
for(int i = 0; i <= 2e5 + 1; i++) num[i] = INF, vec[i] = 0;
vec[0] = 0; b = 1; vec[ ++vec[0] ] = b;
for(int i = 1; i <= 2e5; i++) b = b * a % P, vec[ ++vec[0] ] = b;
sort(vec + 1, vec + 1 + vec[0]);
len = 0;
for(int i = 1; i <= vec[0]; i++) {
if(i == 1 || vec[i] != vec[i - 1]) vec[++len] = vec[i];
}
vec[0] = -INF;
for(int i = len + 1; i <= 2e5 + 1; i++) vec[i] = INF;
b = 1; num[ Find(b) ] = min(num[ Find(b) ], 0ll);
for(int i = 1; i <= 2e5; i++) b = b * a % P, num[ Find(b) ] = min(num[ Find(b) ], i);
for(int i = 1; i <= n; i++) { cin >> s[i] >> d[i]; inv[i] = qpow(s[i], P - 2); }
while(q--) {
int cur, cnt = 0; cin >> cur;
for(int i = 1; i <= n; i++) {
if(s[i] == 0) {
cnt += (cur == 0);
continue;
}
int pos = Find(cur * inv[i] % P);
if(pos >= 1 && pos <= len && num[pos] <= d[i]) cnt++;
}
cout << cnt << "\n";
}
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; cin >> T;
while(T--) Solve();
return 0;
}
H. Shinobu Loves Segment Tree
对于单个操作 \(Build(1,1,i)\),我们将 \(x\) 从次高位计算下去直至最低位,若当前遇到0则往左走,长度上取整;遇到1则往右走,长度下取整。最后直接计算长度即可。
设 \(DFS(l,r,p)\) 表示 \([l,r]\) 这段操作对于 \(x\) 中第 \(p\) 位至第 \(0\) 位的贡献。
对于任意的 \(k\),\(2k\) 和 \(2k+1\) 下取整后长度都变为 \(k\),后续的操作都相同。对于上取整同理。
所以 \(DFS(l, r, p)\) 的答案序列大概长这样: \(k,k+1,k+1,k+2,k+2,...,k+k',k+k'\)
所以 \(DFS(l,r,p)\) 可由两种情况推得:① \(DFS(l/2,r/2,p-1)\) 遇到1,下取整;② \(DFS((l+1)/2,(r+1)/2,p-1)\) 遇到0,上取整。
注意判断 \(l,r\) 的边界情况。
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define fi first
#define se second
#define pii pair<int, int>
#define int long long
using namespace std;
int n, x;
vector<int> xnum;
int DFS(int x, int p) {
if(p < 0) return x;
if(x <= 1) return 0;
if(!xnum[p]) DFS((x + 1) / 2, p - 1);
else return DFS(x / 2, p - 1);
}
int DFS(int l, int r, int p) {
if(l > r) return 0;
if(p < 0) return (r * (r + 1) - (l - 1) * l) / 2;
if(l == 1) ++l;
int cur;
if(!xnum[p]) {
cur = DFS((l + 1) / 2, (r + 1) / 2, p - 1) * 2;
if(l % 2 == 0) cur -= DFS((l + 1) / 2, p - 1);
if(r % 2 == 1) cur -= DFS((r + 1) / 2, p - 1);
} else {
cur = DFS(l / 2, r / 2, p - 1) * 2;
if(l % 2 == 1) cur -= DFS(l / 2, p - 1);
if(r % 2 == 0) cur -= DFS(r / 2, p - 1);
}
return cur;
}
void Solve() {
cin >> n >> x;
if(x == 1) return void(cout << n * (n + 1) / 2 << "\n");
if(n == 1) return void(cout << "0\n");
xnum.clear();
while(x) xnum.push_back(x & 1), x >>= 1;
xnum.pop_back();
cout << DFS(1, n, xnum.size() - 1) << "\n";
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; cin >> T;
while(T--) Solve();
return 0;
}
J. Planar graph
考虑Kruskal的构造过程,枚举所有边并维护一个并查集。
发现添加当前边使得图出现环的时候,两个节点都在一个并查集,平面图刚好需要一个隧道来打通两个区域。
从后往前枚举边,当当前边的两个节点都在一个并查集时添加进答案,最后直接输出答案即可。
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define fi first
#define se second
#define pii pair<int, int>
#define int long long
using namespace std;
const int MAXN = 4e5 + 5;
const int INF = 1e9 + 10;
int f[MAXN], u[MAXN], v[MAXN], n, m;
vector<int> ans;
int find(int u) { return u == f[u] ? u : f[u] = find(f[u]); }
void Solve() {
cin >> n >> m;
for(int i = 1; i <= n; i++) f[i] = i;
for(int i = 1; i <= m; i++) cin >> u[i] >> v[i];
ans.clear();
for(int i = m; i >= 1; i--) {
if( find( u[i] ) == find( v[i] ) ) ans.push_back(i);
else f[ find(u[i]) ] = find(v[i]), f[ find(v[i]) ] = find(u[i]);
}
reverse(ans.begin(), ans.end() );
cout << ans.size() << "\n";
for(auto i : ans) cout << i << " ";
cout << "\n";
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; cin >> T;
while(T--) Solve();
return 0;
}
L. Loop
发现这个操作可以等价于在保证其他数的次序下选一个数插入到后面的位置。
设最大值为 \(Max\), \(a[0]=a[n+1]=Max\) 。
首先先寻找数列中最大值和最大值出现的所有位置,然后从前往后枚举两个最大值之间的区间。
设区间长度为 \(len\), 对每个区间我们有以下操作:
① 若 \(len \leq k\),则把所有的数取出来并令 \(k -= len\)
② 若 \(len > k\),则Check一下这个区间,判断哪些数能被取出来,Check时继续进行以下操作:
- 若 \(a[i+1]>a[i]\),则取出 \(a[i]\)
- 若 \(a[i+1] \leq a[i]\),则暂时保留 \(a[i]\)
- 进行一轮后,若 \(k > 0\),则从后往前取出 \(k\) 个数字
进行操作后,我们将所有取出来的数字降序排序接在原数列后面即可。
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 3e5 + 5;
int n, k, maxx, last, cnt, a[MAXN], num[MAXN], ans[MAXN];
map<int, bool> Map;
vector<int> vecmax, vec;
inline void read(int &X) {
X = 0; int w = 0; char ch = 0;
while(!isdigit(ch)) w |= ch=='-', ch = getchar();
while( isdigit(ch)) X = (X<<3)+ (X<<1) + (ch-48), ch = getchar();
X = w ? -X : X;
}
inline void write(int x) {
static int sta[65];
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while(x);
while(top) putchar(sta[--top] + 48);
}
void Check(vector<int>& vec, int l, int r, int k) {
//cout << l << " " << r << " lr\n";
vec.push_back(a[l]); int sze = 1;
for(int i = l + 1; i <= r; i++) {
while(k && sze && a[i] > vec.back() ) {
num[ vec.back() ]++;
Map[ -vec.back() ] = 1;
vec.pop_back();
k--;
sze--;
}
vec.push_back(a[i]);
sze++;
}
//cout << k << " K\n";
//for(auto i : vec) cout << i << " "; cout << "vec\n";
while(k && sze) {
num[ vec.back() ]++;
Map[ -vec.back() ] = 1;
vec.pop_back();
k--;
sze--;
}
//for(auto i : vec) cout << i << " "; cout << "vec\n";
//for(int i = 1; i <= 10; i++) cout << num[i] << " "; cout << "num\n";
}
void Solve() {
read(n); read(k); maxx = last = cnt = 0;
for(int i = 1; i <= n; i++) read(a[i]), maxx = max(maxx, a[i]);
for(int i = 1; i <= n; i++) {
if(a[i] == maxx) vecmax.push_back(i);
}
vecmax.push_back(n + 1);
for(auto i : vecmax) {
if(k >= i - last - 1) {
k -= (i - last - 1);
for(int j = last + 1; j < i; j++) num[ a[j] ]++, Map[ -a[j] ] = 1;
if(i <= n) ans[++cnt] = maxx;
} else {
Check(vec, last + 1, i - 1, k);
for(auto j : vec) ans[++cnt] = j;
for(int j = i; j <= n; j++) ans[++cnt] = a[j];
break;
}
last = i;
}
for(auto i : Map) {
for(int j = 0; j < num[-i.first]; j++) ans[++cnt] = -i.first;
//cout << -i.first << " c\n";
}
for(int i = 1; i <= n; i++) num[ a[i] ] = 0;
for(int i = 1; i < n; i++) write(ans[i]), putchar(' ');
write(ans[n]);
Map.clear(); vecmax.clear(); vec.clear();
}
signed main()
{
//ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; read(T);
for(int i = 1; i <= T; i++) {
Solve();
if(1) putchar('\n');
}
return 0;
}