大致题意:
给定一个点权为1的树,每次可以选中两个点
l
l
l和
r
r
r,要求这两点间简单路径经过的所有点的权重都为1,进行如下操作:记录此路径包含所有点的权重之和为
s
s
s,并且使得两点之间的简单路径所经过的点权重都变为0。由此得到一个三元组
(
s
,
l
,
r
)
(s,l,r)
(s,l,r)。只要有一个点权重不为0,就一直进行操作。要求:输出可能情况下字典序最大的三元组组成的序列。(好像解释的更乱了QAQ…)
放个题目链接吧。
解:
看到题目直观上想到,首先要保证每次选取的两个点一定是当前连通分量上一条直径的两端,由此保证
s
s
s是当前可取情况中最大的,进而保证字典序最大。每次一条直径可以把当前树划分为森林。
接下来重要的是如何考虑时间复杂度。
对于一次操作,最少也是可以去掉三个顶点的。每次操作去掉的点数一定是小于等于上次的。我们每次都可以去掉一条直径,然后对于剩下的连通分量中的树继续进行删直径操作,直到树空。
code
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define pii pair<int,int>
using namespace std;
const int one = 1;
const double eps = 1e-8;
const int mod = 998244353;
int dx[] = {0, 0, 1, -1}, dy[] = {1, -1, 0, 0};
int lowbit(int x) {
return x & (-x);
}
void solve() {
int n;
cin >> n;
vector<vector<int>> g(n + 1);
for (int i = 1; i < n; i++) {
int a, b;
cin >> a >> b;
g[a].push_back(b);
g[b].push_back(a);
}
vector<int> used(n + 1);
vector<int> fa(n + 1, -1);
vector<array<int, 3>> t;
while (1) {
int temp = 0;
for (int i = 1; i <= n; i++) {
temp += used[i];
}
if (temp == n) break;
function<pii(int, int)> dfs = [&](int x, int pa) {//分别代表 距离 当前点
pii ans = {1, x};
fa[x] = pa;
for (auto ne: g[x]) {
if (ne != pa && !used[ne]) {
auto z = dfs(ne, x);
z.first++;
ans = max(z, ans);//因为要保证字典序 所以距离不同 找距离大的 距离相同的话 找序号大的
}
}
return ans;
};
for (int i = 1; i <= n; i++) {
if (!used[i]) {
auto [a, b] = dfs(i, -1);
auto [c, d] = dfs(b, -1);
t.push_back({c, max(b, d), min(b, d)});
while (d != -1) {//对于所有被判定为直径的,都置为不再访问
used[d] = 1;
d = fa[d];
}
}
}
}
sort(t.begin(), t.end(), [&](array<int, 3> a, array<int, 3> b) {
if (a[0] != b[0]) return a[0] > b[0];
else if (a[1] != b[1]) return a[1] > b[1];
else return a[2] > b[2];
});
for (auto [a, b, c]: t) {
cout << a << " " << b << " " << c << " ";
}
cout << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int T = 1;
cin >> T;
while (T--) solve();
return 0;
}
对于此题的时间复杂度吧,我觉着是n^2,但是为什么能过呢,我也不太清楚。
以后想到了方法先实现再说,tle 了再优化。