2022天梯赛 L2-3,L3-1,L3-2

2022天梯赛 L2-3,L3-2

L2-3 龙龙送外卖

题意

​ 龙龙是“饱了呀”外卖软件的注册骑手,负责送帕特小区的外卖。帕特小区的构造非常特别,都是双向道路且没有构成环 —— 你可以简单地认为小区的路构成了一棵树,根结点是外卖站,树上的结点就是要送餐的地址。

​ 每到中午 12 12 12 点,帕特小区就进入了点餐高峰。一开始,只有一两个地方点外卖,龙龙简单就送好了;但随着大数据的分析,龙龙被派了更多的单子,也就送得越来越累……

​ 看着一大堆订单,龙龙想知道,从外卖站出发,访问所有点了外卖的地方至少一次(这样才能把外卖送到)所需的最短路程的距离到底是多少?每次新增一个点外卖的地址,他就想估算一遍整体工作量,这样他就可以搞明白新增一个地址给他带来了多少负担。

思路

  • 贪心

​ 我们将条件弱化,假设最后送完了以后要回到起点。那么我们每次新加入一个点 u u u,这个 u u u的最优解一定是它不断往上跳,然后跳到某个走过的路径上,那么跳的这段的距离 × 2 \times 2 ×2,就是这个点的贡献,这就是这个点走的路径,我们将它跳的路径标记一下,下次某个点的最优解可能会是这个路径,每次不断的加入新的点然后动态维护最优解,每个点最多遍历一次,因此复杂度为 O ( n ) O(n) O(n)

​ 现在我们可以选择最后不会到起点,贪心的来想我们让最后回去的这个路径越长越好,因此维护一下根结点往下最长的距离即可,最后总和减去这条链即可。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
	e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int dep[N], mx, s;
int fa[N];
bool st[N];
void dfs(int u, int fa, int d) {
	dep[u] = d;
	for (int i = h[u]; ~i; i = ne[i]) {
		int j = e[i];
		if (j == fa) continue ;
		dfs(j, u, d + 1);
	}
}
int main() {
	ios::sync_with_stdio(false), cin.tie(0);
	cin >> n >> m;
	memset(h, -1, sizeof h);
	int root;
	for (int i = 1; i <= n; i ++) {
		int p; cin >> p;
		if (p == -1) root = i;
		else add(i, p), add(p, i), fa[i] = p;
	}

	dfs(root, -1, 0);
	st[root] = true;
	while (m --) {
		int x, y = 0; cin >> x;
		while (!st[x]) {
			st[x] = true;
			x = fa[x];
			y ++;
		}
		
		mx = max(mx, y + dep[x]);
		s += y * 2;
		cout << s - mx << '\n';
	}
	return 0;
}

L3-1 千手观音

题意

​ 从小到大给你 n n n个字符串,每个字符串都代表一个数字,其中每一位用 . . .来区分,先让你确定这个字符串里所有的位的字符相对大小,相对大小不明确的按字典序排。

思路

  • 拓扑排序,优先队列

​ 两个字符串若位数相同且前缀相同则他们第一个不同的位的字符串可以确定大小关系,由于字符串按从小到大给出,因此每个字符串只需和它前面的一个确定关系。我们需要和某个点确定关系的点都在这个点的前面给出,这个可以在确定关系的时候建图,然后拓扑排序即可,对于关系不定的我们要按字典序,这个限制就意味着我们每次要选择入读为 0 0 0且字典序最小的那个点来往后更新,动态找最小可以用个优先队列。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
	e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k, id;
map<string, int> mp;
string gg[N];
int deg[N];
struct Node {
	string str;
	int id;
	bool operator<(Node t)const {
		return str > t.str;
	}
};
int main() {
	ios::sync_with_stdio(false), cin.tie(0);
	cin >> n;
	memset(h, -1, sizeof h);
	vector<string> pre;
	for (int i = 1; i <= n; i ++) {
		vector<string> ve;
		string str; cin >> str;
		str += '.';
		int d = 0;
		for (int i = 0; i < str.size(); i ++) {
			if (str[i] == '.') {
				string t = str.substr(i - d, d);
				if (!mp[t]) mp[t] = ++id, gg[id] = t;
				ve.push_back(t);
				d = 0;
			}
			else d ++;
		}
		if (i == 1) pre = ve;
		else {
			if (pre.size() == ve.size()) {
				for (int i = 0; i < ve.size(); i ++) {
					if (pre[i] != ve[i]) {
						add(mp[pre[i]], mp[ve[i]]);
						deg[mp[ve[i]]] ++;
						break;
					}
				}
			}
			pre = ve;
		}
	}

	priority_queue<Node> pq;
	for (int i = 1; i <= id; i ++)
		if (!deg[i])
			pq.push({gg[i], i});
	
	vector<string> res;
	while (pq.size()) {
		auto t = pq.top();
		pq.pop();
		res.push_back(t.str);
		for (int i = h[t.id]; ~i; i = ne[i]) {
			int j = e[i];
			if (-- deg[j] == 0) {
				pq.push({gg[j], j});
			}
		}
	}

	for (int i = 0; i < res.size(); i ++)
		cout << res[i] << ".\n"[i == res.size() - 1];
	return 0;
}

L3-2 关于深度优先搜索和逆序对的题应该不会很难吧这件事

题意

​ 给定一棵 n n n 个节点的树,其中节点 r r r为根。求该树所有可能的 D F S DFS DFS 序中逆序对数量之和。

思路

  • 树状数组,分类讨论

​ 由于 d f s dfs dfs序的不同只是因为对子节点遍历顺序不同而产生的,因此不改变任意两个结点的父子关系。因此我们将贡献分为两类:1.具有父子关系的点对在 d f s dfs dfs序后产生的逆序对 2.没有父子关系的点对在 d f s dfs dfs序后产生的逆序对

​ 我们设 f [ u ] : 以 u 为 根 的 结 点 有 多 少 种 不 同 的 d f s 序 f[u]: 以u为根的结点有多少种不同的dfs序 f[u]:udfs

​ 对于 f [ u ] f[u] f[u]受两部分影响,它的所有子节点的 d f s dfs dfs序,和子节点遍历顺序的不同(即子节点的排列数),这些都是独立的符合乘法原理,因此 f [ u ] = 所 有 子 节 点 的 d f s 序 和 它 的 子 节 点 数 的 排 列 相 乘 f[u]=所有子节点的dfs序和它的子节点数的排列相乘 f[u]=dfs

​ 对于第一个情况的贡献即: f [ r o o t ] × 总 的 父 子 逆 序 对 数 f[root]\times 总的父子逆序对数 f[root]×,统计树上逆序对数可以用树状数组动态来做,就类似于求在某些限制下比某个数小的数的个数。

​ 对于第二个的贡献,我们这样来想,任意两个不具父子关系的点 x , y x,y x,y,他们一定是一个大一个小,不妨设 x > y x>y x>y,则最终 d f s dfs dfs序中只有 x x x y y y前面的时候会产生贡献,由于是随机的因此 x x x y y y前面的概率只会有 1 2 \frac{1}{2} 21,因此这个点对的贡献就是 f [ r o o t ] × 1 2 f[root]\times \frac{1}{2} f[root]×21,那么第二个情况的贡献就是: f [ r o o t ] × 所 有 没 有 父 子 关 系 的 点 对 f[root]\times 所有没有父子关系的点对 f[root]×

​ 统计所有没有父子关系的点对,可以这样来容斥,首先每个点可以和其它 n − 1 n-1 n1个点构成点对,由于是相互的关系,因此总的点对数位 s = n × ( n − 1 ) / 2 s=n\times(n-1)/2 s=n×(n1)/2,那么我们只需要用 s s s减去存在父子关系的点对即可,存在父子关系的点对可以在求第一个情况时维护出来,即对于每个点求它祖先结点比它大的和比大小的数,这两类数的和就是和这个点构成祖先关系的点。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 3e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add1(int a, int b, int v = 0) {
	e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int cnt1, cnt2;
int f[N], fact[N];
int qmi(int a, int  b) {
	int res = 1;
	while (b) {
		if (b & 1) res = (LL)res * a % mod;
		a = (LL)a * a % mod;
		b >>= 1;
	}
	return res;
}
int tr[N];
void add(int x, int v) {
	for (; x <= n; x += (x & -x)) tr[x] += v;
}
int sum(int x) {
	int res = 0;
	for (; x; x -= (x & -x)) res += tr[x];
	return res;
}
void dfs(int u, int fa) {
	f[u] = 1;
	int ch = 0;
	for (int i = h[u]; ~i; i = ne[i]) {
		int j = e[i];
		if (j == fa) continue ;
		ch ++;
		add(j, 1);
		dfs(j, u);
		cnt1 = ((LL)cnt1 + sum(n) - sum(j)) % mod;
		cnt2 = ((LL)cnt2 + sum(j - 1)) % mod;
		add(j, -1);
		f[u] = (LL)f[u] * f[j] % mod;
	}
	f[u] = (LL)f[u] * fact[ch] % mod;
}
int main() {
	ios::sync_with_stdio(false), cin.tie(0);
	int root;
	cin >> n >> root;
	fact[0] = 1;
	for (int i = 1; i <= n; i ++) fact[i] = (LL)fact[i - 1] * i % mod;
	memset(h, -1, sizeof h);
	for (int i = 1; i < n; i ++) {
		int a, b; cin >> a >> b;
		add1(a, b), add1(b, a);
	}
	add(root, 1);
	dfs(root, -1);
	
	int s = (((LL)n * (n - 1) % mod * qmi(2, mod - 2) % mod - cnt1 - cnt2) % mod + mod) % mod;
	cout << (((LL)f[root] * cnt1 % mod + (LL)f[root] * s % mod * qmi(2, mod - 2) % mod) % mod + mod) % mod;
	return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值