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\) 。易得

\[2 \cdot diff = (\sum vec[w]) \cdot (\sum vec[w]) - (\sum vec[w] \cdot vec[w]) \]

其中 \(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\), 有:

\[\left\{ \begin{array}{**lr**} val += dp[v][0], e < w \\ val += min(dp[v][0], dp[v][1]), e = w \\ val += dp[v][1], e > w \end{array} \right. \]

\(f\)\(u\) 所连边边权为 \(e'\),有

\[\left\{ \begin{array}{**lr**} dp[u][0] = min(dp[u][0], val), w \leq e' \\ dp[u][1] = min(dp[u][1], val), w \geq e' \\ \end{array} \right. \]

#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;
} 
posted @ 2022-08-09 22:08  Orzjh  阅读(117)  评论(0)    收藏  举报