2026-03-04~06 hetao1733837 的刷题记录
定了,以后一三五七学新东西,二四六刷 HT 的题单!
03-04
基环树吧!
LGP2607 [ZJOI2008] 骑士
原题链接:[ZJOI2008] 骑士
分析
好的,基础知识初步具备,开!
问号?这么一说,好像确实存在一种巧妙的图论建模......我不会
思考一下,如果我们把某一个骑士和他讨厌的骑士连边,显然会出现一个有向的基环树。
然后呢?说很类似 LGP1352?像在哪?喔!!!太妙了!
那么,把一个骑士讨厌的骑士看成他的父亲......好怪啊......设一个骑士是 a a a,他讨厌的是 b b b,在树上, b b b 作为 a a a 的父亲。连一条 b → a b\rightarrow a b→a 的有向边。则形成基环树森林,套用 LGP1352 即可。
正解
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1000005;
int val[N];
long long dp[N][2];
struct node{
int nxt, to;
}e[N << 1];
int head[N << 1], vis[N], n, s, tot, res1, res2, cur;
void add(int x, int y){
e[tot].to = y;
e[tot].nxt = head[x];
head[x] = tot++;
}
void check(int u, int pre){
vis[u] = 1;
for (int i = head[u]; ~i; i = e[i].nxt){
if ((i ^ 1) == pre)
continue;
if (vis[e[i].to]){
res1 = u;
res2 = e[i].to;
cur = i;
continue;
}
check(e[i].to, i);
}
}
void dfs(int u, int pre){
dp[u][0] = 0;
dp[u][1] = val[u];
for (int i = head[u]; ~i; i = e[i].nxt){
if ((i ^ 1) == pre)
continue;
if (i == cur || (i ^ 1) == cur)
continue;
dfs(e[i].to, i);
dp[u][1] += dp[e[i].to][0];
dp[u][0] += max(dp[e[i].to][1], dp[e[i].to][0]);
}
}
signed main(){
scanf("%d", &n);
memset(head, -1, sizeof(head));
for (int i = 1, w, b; i <= n; i++){
scanf("%d%d", &w, &b);
add(i, b);
add(b, i);
val[i] = w;
}
long long ans = 0;
for (int i = 1; i <= n; i++){
if (vis[i])
continue;
check(i, -2);
dfs(res1, -1);
long long tmp = dp[res1][0];
dfs(res2, -1);
tmp = max(tmp, dp[res2][0]);
ans += tmp;
}
printf("%lld", ans);
}
调红温了......
03-05
LGP5049 [NOIP 2018 提高组] 旅行 加强版
分析
OK,我了解了......就是要么是树,要么是基环树,因为联通且 m ≤ n m\le n m≤n。
使得字典序最小?要不要分讨呢?即分讨树和基环树。不怕麻烦的话,可以这样去思考。
先想一下树,由于他有一个非常好的操作,即退回上一个,所以......呢?哦,新城市才记录,那岂不是一个贪子?
基环树其实应该先按照树来做,然后调整一部分即可?那难道一个 dfs 就解决了?我不信!
bur ,那没有加强的岂不是可以直接 O ( n 2 ) O(n^2) O(n2)? Σ(っ °Д °;)っ
搓了一个错的暴力......
显然,对于一棵纯正的树,直接 d f s dfs dfs 即可,对于基环树,问题在于如何对环进行操作,其实,在环上走的时候,只有当其出边是环上的最大的点,且回溯后的点比他小的时候,我们回溯,反之,和真正的树没有任何区别。
自己写一写代码。
好的,没写基环树的情况下获得 60 p t s 60pts 60pts,数据还是很良心的。
正解
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 500005;
int n, m;
struct node{
int u, v;
}g[N << 1];
int head[N], tot;
struct edge{
int nxt, to;
}e[N << 1];
void add(int x, int y){
e[++tot].to = y;
e[tot].nxt = head[x];
head[x] = tot;
}
int ans[N], top;
bool vis[N];
void dfs1(int u){
vis[u] = true;
ans[++top] = u;
for (int i = head[u]; i; i = e[i].nxt){
int v = e[i].to;
if (vis[v])
continue;
dfs1(v);
}
}
bool cmp(node a, node b){
return a.v > b.v;
}
int fa[N];
bool flag, ring[N];
void dfs2(int u, int pa){
if (flag)
return ;
if (fa[u] == 0){
fa[u] = pa;
}
else if (fa[u] != pa){
while (u != pa){
ring[pa] = true;
pa = fa[pa];
}
ring[u] = true;
flag = true;
return ;
}
for (int i = head[u]; i; i = e[i].nxt){
int v = e[i].to;
if (v == pa)
continue;
dfs2(v, u);
}
}
bool lst;
int pre_mx;
void dfs3(int u){
vis[u] = true;
ans[++top] = u;
if (ring[u]){
bool fl = false;
for (int i = head[u]; i; i = e[i].nxt){
if (lst)
break;
int v = e[i].to;
if (vis[v])
continue;
if (ring[v]){
i = e[i].nxt;
while (vis[e[i].to])
i = e[i].nxt;
if (i)
pre_mx = e[i].to;
else if (v > pre_mx){
fl = true;
lst = true;
}
break;
}
}
for (int i = head[u]; i; i = e[i].nxt){
int v = e[i].to;
if (vis[v])
continue;
if (ring[v] && fl)
continue;
dfs3(v);
}
}
else{
for (int i = head[u]; i; i = e[i].nxt){
int v = e[i].to;
if (vis[v])
continue;
dfs3(v);
}
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1, u, v; i <= m; i++){
cin >> u >> v;
g[i] = {u, v};
g[i + m] = {v, u};
}
sort(g + 1, g + 2 * m + 1, cmp);
for (int i = 1; i <= (m << 1); i++){
add(g[i].u, g[i].v);
}
if (m == n - 1){
dfs1(1);
for (int i = 1; i <= top; i++){
cout << ans[i] << " ";
}
return 0;
}
else{
dfs2(1, 1);
flag = false;
pre_mx = 0x3f3f3f3f;
dfs3(1);
for (int i = 1; i <= top; i++)
cout << ans[i] << " ";
}
}
顺手把 LGP5022 [NOIP 2018 提高组] 旅行 也交了。
LGP2947 [USACO09MAR] Look Up S
分析
一眼单调栈!
正解
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int n, h[N], stk[N];
int ans[N];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++){
cin >> h[i];
}
for (int i = n - 1; i >= 1; i--){
int j = i + 1;
while (h[i] >= h[j] && h[j] > 0)
j = ans[j];
ans[i] = j;
}
for (int i = 1; i <= n; i++){
cout << ans[i] << '\n';
}
}
03-06
LGP4381 [IOI 2008] Island
原题链接:[IOI 2008] Island
分析
说实话,场上看到这种题,我大概率想不到基环树,谁家把重要性质写到输入格式里?
OK ,我手模一下样例。
模出来了,能说明什么呢?不如先从树开始思考。其实是森林吧......
难道说......但是怎么会是动态规划呢?看一眼题解吧......
每个点只经过一次,且最大,这不是 直径 吗?糖丸了!
那么,找出环,把环看作根,向下搜出最长链,合并的时候,掏一个单调队列优化即可。
正解
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1000005;
int n;
struct edge{
int nxt, to, val;
}e[N << 1];
int head[N], tot;
void add(int x, int y, int z){
e[++tot].to = y;
e[tot].nxt = head[x];
e[tot].val = z;
head[x] = tot;
}
int cnt, st;
int vis1[N], vis2[N], rnd[N], sum[N];
bool check(int u, int pre){
if (vis1[u] == 1){
vis1[u] = 2;
rnd[++cnt] = u;
vis2[u] = 1;
return true;
}
vis1[u] = 1;
for (int i = head[u]; i; i = e[i].nxt){
int v = e[i].to;
if (i != ((pre - 1) ^ 1) + 1){
if (check(v, i)){
if (vis1[u] != 2){
rnd[++cnt] = u;
vis2[u] = 1;
sum[cnt] = sum[cnt - 1] + e[i].val;
}
else{
sum[st - 1] = sum[st] - e[i].val;
return false;
}
return true;
}
}
}
return false;
}
int d[N], ans;
void dfs(int u){
vis2[u] = 1;
for (int i = head[u]; i; i = e[i].nxt){
int v = e[i].to;
if (vis2[v])
continue;
dfs(v);
ans = max(ans, d[u] + d[v] + e[i].val);
d[u] = max(d[u], d[v] + e[i].val);
}
}
int dp[N << 1];
int calc(int rt){
st = cnt + 1;
int res1 = 0, res2 = 0;
check(rt, 0);
for (int i = st; i <= cnt; i++){
ans = 0;
dfs(rnd[i]);
res1 = max(res1, ans);
dp[i] = d[rnd[i]];
}
int len = cnt - st + 1;
for (int i = st; i <= cnt; i++){
dp[i + len] = dp[i];
sum[i + len] = sum[i + len - 1] + (sum[i] - sum[i - 1]);
}
deque<int> q;
for (int i = st; i <= st + 2 * len - 1; i++){
while (!q.empty() && q.front() <= i - len)
q.pop_front();
if (!q.empty()){
res2 = max(res2, dp[i] + dp[q.front()] + sum[i] - sum[q.front()]);
}
while (!q.empty() && dp[q.back()] - sum[q.back()] <= dp[i] - sum[i])
q.pop_back();
q.push_back(i);
}
return max(res1, res2);
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1, v, w; i <= n; i++){
cin >> v >> w;
add(i, v, w);
add(v, i, w);
}
int res = 0;
for (int i = 1; i <= n; i++){
if (!vis2[i]){
res += calc(i);
}
}
cout << res;
return 0;
}