2022“杭电杯”中国大学生算法设计超级联赛(7) 题解
C. Counting Stickmen
考虑固定身体,利用乘法原理求解方案数。
首先预处理出入度并存入 \(in\) 数组。
首先找到与 \(u\) 相连的所有节点 \(v\) (包括父亲节点),并求得除 \(u\) 外有多少个点与 \(v\) 相连,易得此值为 \(in[v]-1\) 。
设 \(vec[v] = in[v]-1\) ,考虑枚举 \(v\),此时能固定身体,构成腿的方案为 \(\C_{in[v]-1}^2\)。
考虑如何计算手臂数量,我们需要考虑一个问题:在与 \(u\) 相连的所有节点 \(v\) 中选出两个点 \(v_1,v_2\) ,然后再将 \(u\) 和 \(v_1,v_2\) 的线段延伸出去作为手臂。求出有多少种方案构造出两条手臂,设其为 \(diff\) 。易得
其中 \(w\) 为所有与 \(u\) 相连的节点。
枚举 \(v\) 时,构成手臂的方案有 \(diff - vec[v] \cdot ((\sum vec[w]) - vec[v])\)。
构成头的方案则有 \(in[u]-3\) 个。
利用乘法原理则可求出火柴人的数量。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 5e5 + 5;
const int MOD = 998244353;
int n, in[MAXN], ans, inv2;
vector<int> vec[MAXN], G[MAXN];
int qpow(int a, int p) {
int res = 1;
while(p) {
if(p & 1) res = res * a % MOD;
a = a * a % MOD;
p >>= 1;
}
return res;
}
void DFS(int u, int f) {
for(auto v : G[u]) {
if(v != f) DFS(v, u);
if(in[v] - 1 > 0) vec[u].push_back(in[v] - 1);
}
int sum1 = 0, sum2 = 0, diff = 0;
for(auto i : vec[u]) {
sum1 += i, sum1 %= MOD;
sum2 += i * i % MOD, sum2 %= MOD;
}
diff = (sum1 * sum1 % MOD - sum2 + MOD) % MOD;
diff = diff * inv2 % MOD;
int sze = vec[u].size();
if(in[u] - 3 <= 0) return ;
for(auto i : vec[u]) {
if(i < 2) continue;
int legs = i * (i - 1) % MOD * inv2 % MOD;
int arms = (diff - i * (sum1 - i + MOD) % MOD + MOD) % MOD;
ans += legs * arms % MOD * (in[u] - 3) % MOD;
ans %= MOD;
}
}
void Solve() {
cin >> n; ans = 0;
for(int i = 1; i <= n; i++) G[i].clear(), vec[i].clear(), in[i] = 0;
for(int i = 1; i < n; i++) {
int u, v; cin >> u >> v;
in[u]++, in[v]++;
G[u].push_back(v), G[v].push_back(u);
}
DFS(1, 0);
cout << ans << "\n";
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
inv2 = qpow(2, MOD - 2);
int T; cin >> T;
while(T--) Solve();
return 0;
}
D. Black Magic
贪心,大力分类讨论即可。
-
考虑最小值:
直接将B类型的砖块连在一起。如果存在L或R类型的砖块则将这一串砖块接在L砖块的左侧或R砖块的右侧即可,不存在则直接与E砖块连在一起。
-
考虑最大值:
首先将L,R砖块合并,相同类型的L砖块或相同类型的R砖块放在一起肯定不会被合并。考虑类似于 RR...RRELL...LL 的构造方案,首先在中间放一个E砖块肯定不会消去中间的LR。在R砖块左侧(L砖块右侧)先接一个B,然后再接一个E,这样依次进行下去可以尽可能地保留所有砖块,且最后要么剩下B砖块,要么剩下E砖块。对最后的情况判断一下即可。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int E, L, R, B, Min, Max;
void Solve() {
cin >> E >> L >> R >> B;
Min = E;
if(B) {
if(L || R) Min += max(L, R);
else Min += 1;
} else {
Min += max(L, R);
}
if(!E) {
if(!B) Max = R + L;
else Max = R + L + 1;
} else if(B <= 2) {
Max = R + L + E + B;
} else {
B -= 2; E -= 1;
Max = R + L + 1 + 2;
if(E >= B) Max += B + E;
else Max += E * 2;
}
cout << Min << " " << Max << "\n";
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; cin >> T;
while(T--) Solve();
return 0;
}
F. Sumire
从题目数据入手,设 \(B=2,L=1,R=10^{18}\),则粗略估计后,可得单个数字中数位 \(d\) 的出现次数一定小于 \(64\) 种。
设 \(f[l][r][w]\) 为 \([l,r]\) 中,数位 \(d\) 出现次数为 \(w\) 的方案数有多少个。这边可以通过一次DFS解决。
设 \(DFS(l,r)\) 表示 \([l,r]\) 中,\(f[l][r][w]\) 的所有方案。则可以分为两种情况讨论:
- \([l,r]\) 中的所有数,除最低位以外的其他数位都相同。此时可以将最低位拿出来,先判断其他数位中 \(d\) 出现了多少次,设其为 \(cur\) 。再判断最低位区间 \([ \ l \% B, \ r \% B \ ]\) 中是否含有 \(d\)。否的话有 \(f[l][r][cur]=r-l+1\),是的话则有 \(f[l][r][cur]=r-l,f[l][r][cur+1]=1\)。
- \([l,r]\) 中的所有数,除最低位以外的其他数位不一定相同,将其拆成三部分大力讨论:
- \([l, (l / B + 1) * B - 1]\) 和 \([(r / B) * B, r]\),跟上一种情况相同。
- \([(l / B + 1) * B, (r / B - 1) * B]\),直接对 \([l / B + 1, r / B - 1]\) 进行DFS,将这个DFS得到的结果继续根据最低位处理。
- 当最低位为 \(d\) 时,有 \(f[(l / B + 1) * B][(r / B - 1) * B][w+1] += f[l / B + 1][r / B - 1][w]\);
- 当最低位不为 \(d\) 时,有 \(f[(l / B + 1) * B][(r / B - 1) * B][w] += f[l / B + 1][r / B - 1][w] * (B-1)\)。
最坏时间复杂度约为 \(O(log_B^2 r)\)
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define int long long
#define fi first
#define se second
#define pii pair<int, int>
using namespace std;
const int MAXN = 5e5 + 5;
const int MOD = 1e9 + 7;
int K, B, D, L, R;
map<pii, bool> Vis;
map<pii, unordered_map<int, int> > Dp;
int qpow(int a, int p) {
if(a == 0 && p == 0) return 0;
int res = 1;
while(p) {
if(p & 1) res = res * a % MOD;
a = a * a % MOD;
p >>= 1;
}
return res;
}
void DFS(int l, int r, int p) {
int ll = l, rr = r;
if(!l) l = 1;
if(!r || l > r) return ;
if(Vis[{l, r}]) return ;
int cur = 0, val;
if(l / B == r / B) {
cur = 0, val = l / B;
while(val) cur += (val % B == D), val /= B;
l %= B, r %= B;
if(l <= D && D <= r) {
Dp[{ll, rr}][cur] = r - l;
Dp[{ll, rr}][cur] %= MOD;
Dp[{ll, rr}][cur + 1] = 1;
} else {
Dp[{ll, rr}][cur] = r - l + 1;
Dp[{ll, rr}][cur] %= MOD;
}
} else {
DFS(l / B + 1, r / B - 1, p + 1);
DFS(l, (l / B + 1) * B - 1, p);
DFS((r / B) * B, r, p);
for(auto& i : Dp[{l / B + 1, r / B - 1}]) {
Dp[{ll, rr}][i.fi] += i.se * (B - 1) % MOD;
Dp[{ll, rr}][i.fi + 1] += i.se;
Dp[{ll, rr}][i.fi] %= MOD;
Dp[{ll, rr}][i.fi + 1] %= MOD;
}
for(auto& i : Dp[{l, (l / B + 1) * B - 1}]) {
Dp[{ll, rr}][i.fi] += i.se;
Dp[{ll, rr}][i.fi] %= MOD;
}
for(auto& i : Dp[{(r / B) * B, r}]) {
Dp[{ll, rr}][i.fi] += i.se;
Dp[{ll, rr}][i.fi] %= MOD;
}
}
Vis[{ll, rr}] = 1;
return ;
}
void Solve() {
cin >> K >> B >> D >> L >> R;
Vis.clear(), Dp.clear();
DFS(L, R, 0);
int ans = 0;
for(auto& i : Dp[{L, R}]) ans = (ans + qpow(i.fi, K) * i.se ) % MOD;
cout << ans << "\n";
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; cin >> T;
while(T--) Solve();
return 0;
}
G. Weighted Beautiful Tree
首先考虑一件事,对于每个点权,我们有两种选择:
① 不修改
② 修改至和所连的某条边权一样,否则会导致浪费
可以先将所有边权和自身点权存起来并排序,枚举每个权值并求出最小代价。
设 \(dp[u][0/1]\) 表示处理完以 \(u\) 为根的子树,\(wn_u\) 为当前权值,且这个权值小于等于/大于等于其父边边权的最小代价。
对当前节点与所有儿子连的边按边权排序,设当前节点为 \(u\),父亲为 \(f\), 儿子为 \(v\),枚举到的权值为 \(w\), \(u\) 与 \(v\) 所连边边权为 \(e\),所有儿子在当前权值的最小代价为 \(val\), 有:
设 \(f\) 与 \(u\) 所连边边权为 \(e'\),有
#include<bits/stdc++.h>
#define int long long
#define pii pair<int, int>
#define fi first
#define se second
using namespace std;
const int MAXN = 1e5 + 5;
const int INF = 1e18;
int n, c[MAXN], wn[MAXN], f[MAXN][3], fw[MAXN];
vector<pii> vec[MAXN], G[MAXN];
vector<int> val[MAXN];
void DFS(int u, int fa) {
for(auto& i : G[u]) {
int v = i.se, w = i.fi;
if(v == fa) continue;
fw[v] = w;
DFS(v, u);
vec[u].push_back({w, v});
val[u].push_back(w);
}
val[u].push_back(wn[u]);
val[u].push_back(fw[u]);
sort(val[u].begin(), val[u].end());
sort(vec[u].begin(), vec[u].end());
reverse(vec[u].begin(), vec[u].end());
int pre = 0, suf = 0;
for(auto& i : vec[u]) suf += f[i.se][1];
f[u][0] = f[u][1] = INF;
for(auto& i : val[u]) {
int cost = c[u] * abs(wn[u] - i), preadd = 0;
while(vec[u].size() && vec[u].back().fi <= i) {
int v = vec[u].back().se;
cost += min(f[v][0], f[v][1]);
preadd += f[v][0];
suf -= f[v][1];
vec[u].pop_back();
}
cost += pre + suf;
if(i <= fw[u]) f[u][0] = min(f[u][0], cost);
if(i >= fw[u]) f[u][1] = min(f[u][1], cost);
pre += preadd;
}
}
void Solve() {
cin >> n;
for(int i = 1; i <= n; i++) cin >> c[i];
for(int i = 1; i <= n; i++) cin >> wn[i];
for(int i = 1; i < n; i++) {
int u, v, w; cin >> u >> v >> w;
G[u].push_back({w, v});
G[v].push_back({w, u});
}
DFS(1, 0);
cout << min(f[1][0], f[1][1]) << "\n";
for(int i = 0; i <= n; i++) G[i].clear(), vec[i].clear(), val[i].clear();
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; cin >> T;
while(T--) Solve();
return 0;
}

浙公网安备 33010602011771号