2025-11-25~26 hetao1733837的刷题记录
11.25
LG3620 [APIO/CTSC2007] 数据备份
原题链接:[APIO/CTSC2007] 数据备份
分析
似乎找到了找反悔贪心题目的方法,tag 选则 greedy+priority_queue,选择绿题以上难度,似乎比较合适。
我来思考一下这一题。难道对于每个位置,钦定必须选某个距离,然后依次添加后面的最小值?hyw,有点假啊,真正做到了卡在贪心和 DP 之间,🤮
但是实现似乎并非需要用到堆啊......直接排个序,然后乱做一下,乱WA一下。
按照 fqh 说的,我们思考一下实现可能性,很好实现,直接存储 id+排序+向后枚举即可。但是似乎并非很真啊。
确实很不错的反悔贪心题。一般我们都会想到直接拖出来最小距离,把两边标记为无法使用,这样贪心下去。但是,我们可以构造出这样一组hack:
input
5 2
2 3 5 14
output
4
如果按照错误的贪心策略,我们会直接取 (3-2),然后取 (14-5)。发现答案是 10。但是,如果我们依次取 (2-0) 和 (5-3),那么情况大不一样,直接变成了8。考虑是怎么反悔的。那么,我们在每次取出某一位之后,直接把其两侧距离之后与当前距离只差加入优先队列,这样变完成了反悔。此处用链表时间复杂度较为优秀。
正解
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int n, k, s[N];
struct node{
int v, id;
bool operator < (const node k) const{
return v > k.v;
}
};
priority_queue<node> q;
bool vis[N];
struct LIST{
int v, l, r;
}lst[N];
void delt(int p){
lst[p].l = lst[lst[p].l].l;
lst[p].r = lst[lst[p].r].r;
lst[lst[p].l].r = p;
lst[lst[p].r].l = p;
}
int main(){
cin >> n >> k;
cin >> s[0];
for (int i = 1; i < n; i++){
cin >> s[i];
lst[i].l = i - 1;
lst[i].r = i + 1;
lst[i].v = s[i] - s[i - 1];
q.push((node){s[i] - s[i - 1], i});
}
lst[0].v = lst[n].v = 0x3f3f3f3f;
int ans = 0;
for (int i = 1; i <= k; i++){
while (vis[q.top().id])
q.pop();
node tmp = q.top();
q.pop();
ans += tmp.v;
vis[lst[tmp.id].l] = vis[lst[tmp.id].r] = 1;
lst[tmp.id].v = lst[lst[tmp.id].l].v + lst[lst[tmp.id].r].v - lst[tmp.id].v;
q.push((node){lst[tmp.id].v, tmp.id});
delt(tmp.id);
}
cout << ans;
}
LG1053 [NOIP 2005 提高组] 篝火晚会
原题链接:[NOIP 2005 提高组] 篝火晚会
分析
何意味?贪心在哪?反悔又在哪?应该先把最终的环建出来,然后判断是否能建出来,能就贪心换位(不需要连续,这个我意识到了),否则判 -1?这不是......代码能力的问题?所以我写不出来,(●'◡'●)
呃,代码可实现性......真的好实现吗?哪一个链表?那怎么判非法?
比较接近吗?破环为链,然后,最优的初始形态一定是与最终形态最接近的,那么,以类似旋转的操作(分讨顺逆时针),啊吧啊吧就做完了?这是何意味啊?
正解
蓝在哪?
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 50005;
int n, l[N], r[N], a[N], b[N], c[N], maxn;
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++){
cin >> l[i] >> r[i];
}
a[1] = 1;
a[2] = r[1];
a[n] = l[1];
for (int i = 3; i <= n; i++){
if (l[a[i - 1]] == a[i - 2]){
a[i] = r[a[i - 1]];
continue;
}
if (r[a[i - 1]] == a[i - 2]){
a[i] = l[a[i - 1]];
continue;
}
cout << -1;
return 0;
}
for (int i = 1; i <= n; i++){
b[(i - a[i] + n) % n]++;
c[(i + a[i] + n) % n]++;
}
for (int i = 0; i <= n; i++){
maxn = max({maxn, b[i], c[i]});
}
cout << n - maxn;
}
LG9871 [NOIP2023] 天天爱打卡
原题链接:[NOIP2023] 天天爱打卡
分析
何意味,最开始射了一个三位状态: d p i , j , 0 / 1 dp_{i,j,0/1} dpi,j,0/1 表示第 i i i 天,连续打卡了 j j j 天,第 i i i 天打卡/不打卡的最高能量。
显然,转移太暴力了,我们先优化状态。但是初步估算能拿将近 40pts!
发现可以直接砍到一维------具体原因我感觉感性上来说是连续多少天似乎可以在奖励任务中直接控制。
设 d p i dp_i dpi 表示考虑前 i i i 天的最大值。转移极其显然,
d p i = max ( d p i , d p j − 2 − ( i − j + 1 ) × d + w j , i ) dp_i=\max(dp_i, dp_{j-2}-(i-j+1)\times d+w_{j,i}) dpi=max(dpi,dpj−2−(i−j+1)×d+wj,i)
其中, w j , i w_{j,i} wj,i 表示区间 [ j , i ] [j,i] [j,i] 的贡献。
然后,观察到数据范围稍显巨大,但是并不稠密,所以,做一下离散化。
接着发现,上面的转移方程依赖于区间 max \max max,以及任务区间的区间修改!上线段树!
正解
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 200005;
int c, t, n, m, k, d;
struct node{
int x, y, v;
}a[N];
long long dp[N];
int tmp[N << 1];
int b[N << 1];
struct segtree{
long long mx[N << 2], tag[N << 2];
void update(int p, long long v){
mx[p] += v;
tag[p] += v;
}
void down(int p){
if (tag[p]){
update(p << 1, tag[p]);
update(p << 1 | 1, tag[p]);
tag[p] = 0;
}
}
void pushup(int p){
mx[p] = max(mx[p << 1], mx[p << 1 | 1]);
}
void build(int p, int l, int r){
mx[p] = tag[p] = 0;
if (l == r)
return ;
int mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
}
void modify(int p, int l, int r, int s, int t, long long v){
if (s <= l && r <= t){
update(p, v);
return ;
}
down(p);
int mid = (l + r) >> 1;
if (s <= mid)
modify(p << 1, l, mid, s, t, v);
if (t > mid)
modify(p << 1 | 1, mid + 1, r, s, t, v);
pushup(p);
}
long long query(int p, int l, int r, int s, int t){
if (s > t)
return 0;
if (s <= l && r <= t){
return mx[p];
}
down(p);
int mid = (l + r) >> 1;
long long ans = -1e18;
if (s <= mid)
ans = max(ans, query(p << 1, l, mid, s, t));
if (t > mid)
ans = max(ans, query(p << 1 | 1, mid + 1, r, s, t));
return ans;
}
}T;
vector<pair<int, int>> e[N];
int len = 0;
int find(int x){
int l = 1, r = len, ans = r;
while (l <= r){
int mid = (l + r) >> 1;
if (b[mid] >= x){
ans = mid;
r = mid - 1;
}
else{
l = mid + 1;
}
}
return ans;
}
long long work(int x){
if (x >= 0 && x <= len)
return dp[x];
return 0;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> c >> t;
while (t--){
len = 0;
cin >> n >> m >> k >> d;
tmp[0] = 0;
for (int i = 1; i <= m; i++){
cin >> a[i].x >> a[i].y >> a[i].v;
a[i].y = a[i].x - a[i].y + 1;
tmp[i * 2 - 1] = a[i].y;
tmp[i * 2] = a[i].x;
}
sort(tmp + 1, tmp + 2 * m + 1);
for (int i = 1; i <= m * 2; i++){
if (i == 1 || tmp[i] != tmp[i - 1])
b[++len] = tmp[i];
}
T.build(1, 1, len);
for (int i = 0; i <= len; i++) {
dp[i] = 0;
e[i].clear();
}
for (int i = 1; i <= m; i++){
a[i].y = lower_bound(b + 1, b + len + 1, a[i].y) - b;
a[i].x = lower_bound(b + 1, b + len + 1, a[i].x) - b;
e[a[i].x].push_back({a[i].y, a[i].v});
}
for (int i = 1; i <= len; i++){
for (auto o : e[i])
T.modify(1, 1, len, 1, o.first, o.second);
int pos = find(b[i] - k + 1);
long long q1 = T.query(1, 1, len, pos, i - 1);
dp[i] = max(dp[i], q1 - 1LL * b[i] * d - d);
int pos2 = i - 2;
if (b[i - 1] != b[i] - 1)
pos2 = i - 1;
long long q2 = T.query(1, 1, len, i, i);
dp[i] = max(dp[i], q2 + work(pos2) - d);
dp[i] = max(dp[i], dp[i - 1]);
T.modify(1, 1, len, i, i, work(pos2) + 1LL * b[i] * d);
}
cout << dp[len] << '\n';
}
return 0;
}
3.48KB,看笑了。
11.26
LG11363 [NOIP2024] 树的遍历
原题链接:[NOIP2024] 树的遍历
分析
方法一------硬推
新定义是**!
然后我们推出极其错误的式子
∏ ( s o n u × ( b r o u − v i s u ) ) \prod (son_u\times (bro_u-vis_u)) ∏(sonu×(brou−visu))
好🍬啊!
那么,看一下题解吧!
注意到,当两棵生成树相同的时候,他们的根一定是原树叶子之间连的链。
当链确定的时候,似乎差的并不远,答案是:
∏ ( d i − 1 ) ! × ∏ ( d v − 1 ) − 1 \prod (d_i-1)!\times \prod(d_v-1)^{-1} ∏(di−1)!×∏(dv−1)−1
d i d_i di 表示度数,认为 0 − 1 = 1 0^{-1}=1 0−1=1, v v v 是链上的点。
问题转化为有一棵树,边权有0/1,点有点权,求所有叶子到叶子的链,满足这条链上有一条1边,点权乘积和多少。
何意味?
让 aoao 帮我讲了一下,有了新的理解。
首先考虑 k = 1 k=1 k=1 的情况------入边一定,答案显然: ∏ ( d i − 1 ) ! \prod(d_i-1)! ∏(di−1)!,其中 d i d_i di 表示度数。
考虑 k > 1 k>1 k>1 的情况,
也就是说,我们找出一颗生成树,然后,发现上面某些节点作为初始点,生成树一样,而这些初始点,~~经过观察、RP、玄学······~~可以得出,他们都在树上的一条极长链上,然后,上面的式子就自然地成立了,🐂🍺!
然后对所有合法的求和即可,并非很难,可以 D P DP DP,结束?
cpp
#include<bits/stdc++.h>
#define mod 1000000007
using namespace std;
const int N = 100005;
int n, k, u[N], v[N], tag[N], d[N];
vector<pair<int, int>> e[N];
long long sum[N][2], inv[N], ans;
void dfs(int u, int fa){
sum[u][0] = sum[u][1] = 0;
int v;
long long val = 0;
for (auto tmp : e[u]){
v = tmp.first;
if (v == fa)
continue;
dfs(v, u);
if (tmp.second){
sum[v][1] += sum[v][0];
sum[v][0] = 0;
}
val = (val + sum[v][1] * (sum[u][0] + sum[u][1]) + sum[v][0] * sum[u][1]) % mod;
sum[u][0] = (sum[u][0] + sum[v][0]) % mod;
sum[u][1] = (sum[u][1] + sum[v][1]) % mod;
}
long long INV = inv[d[u]];
ans = (ans + INV * val) % mod;
if (!d[u])
sum[u][0]++;
sum[u][0] = (sum[u][0] * INV) % mod;
sum[u][1] = (sum[u][1] * INV) % mod;
}
int c, t;
int main(){
inv[0] = inv[1] = 1;
for (int i = 2; i < N; i++)
inv[i] = inv[mod % i] * (mod - mod / i) % mod;
cin >> c >> t;
while (t--){
cin >> n >> k;
for (int i = 1; i <= n; i++){
d[i] = -1;
e[i].clear();
}
ans = 0;
for (int i = 1; i < n; i++){
cin >> u[i] >> v[i];
tag[i] = 0;
d[u[i]]++;
d[v[i]]++;
}
while (k--){
int E;
cin >> E;
tag[E] = 1;
}
if (n == 2){
cout << 1 << '\n';
continue;
}
for (int i = 1; i < n; i++){
e[u[i]].push_back({v[i], tag[i]});
e[v[i]].push_back({u[i], tag[i]});
}
int root = 0;
for (int i = 1; i <= n; i++)
if (d[i])
root = i;
dfs(root, 0);
for (int i = 1; i <= n; i++){
for (int j = 1; j <= d[i]; j++){
ans = ans * j % mod;
}
}
cout << ans << '\n';
}
}
s u m u , 0 / 1 sum_{u,0/1} sumu,0/1 表示 u u u 子树内,叶子到它有/没有 1 1 1 边的乘积总和,也相当于树形 D P DP DP。
写红了,对容斥并非很有兴趣。
方法二------容斥
本质相同,其中, f f f 相当于 s u m sum sum,不过多赘述。
LG11364 [NOIP2024] 树上查询
原题链接:[NOIP2024] 树上查询
分析
何意味?据说要用到虚树的思想,我来看看!
由定义知区间 LCA 深度为 min l ≤ i ≤ r d e L C A ( i , i + 1 ) \min\limits_{l\le i\le r}de_{LCA(i,i+1)} l≤i≤rmindeLCA(i,i+1)
我们找出以 L C A ( i , i + 1 ) LCA(i,i+1) LCA(i,i+1) 为 LCA 的最大区间 [ x i , y i , v i ] [x_i,y_i,v_i] [xi,yi,vi], v i v_i vi表示 d e L C A ( i , i + 1 ) de_{LCA(i,i+1)} deLCA(i,i+1)。
那么,查询是求与 [ l , r ] [l,r] [l,r] 交集至少为 k k k,且最大的 v i v_i vi,列出不等式:
{ y i ≥ r x i ≤ r − k + 1 \left\{\begin{matrix} y_i\ge r \\ x_i \le r-k+1 \end{matrix}\right. {yi≥rxi≤r−k+1
{ l + k − 1 ≤ y i ≤ r y i − x i + 1 ≥ k \left\{\begin{matrix} l+k-1\le y_i \le r \\ y_i-x_i+1\ge k \end{matrix}\right. {l+k−1≤yi≤ryi−xi+1≥k
第一个对 r r r 扫描线,第二个对 k k k 扫描线,时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)。
何意味?
正解
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 500005, K = 20;
int n, q;
vector<int> e[N];
struct node{
int l, r, d;
};
vector<node> qk[N], qr[N];
int de[N], fa[N], sz[N], son[N], top[N];
void dfs1(int u, int f){
de[u] = de[f] + 1;
fa[u] = f;
sz[u] = 1;
for (auto v : e[u]){
if (v == fa[u])
continue;
dfs1(v, u);
sz[u] += sz[v];
if (sz[v] > sz[son[u]])
son[u] = v;
}
}
void dfs2(int u, int f){
top[u] = f;
if (son[u])
dfs2(son[u], f);
for (auto v : e[u]){
if (v == fa[u] || v == son[u])
continue;
dfs2(v, v);
}
}
int LCA(int x, int y){
while (top[x] != top[y]){
if (de[top[x]] < de[top[y]])
swap(x, y);
x = fa[top[x]];
}
return de[x] < de[y] ? x : y;
}
int a[N], ans[N];
struct node1{
int x, y, v;
}b[N];
struct segtree{
int mx[N << 2];
void pushup(int p){
mx[p] = max(mx[p << 1], mx[p << 1 | 1]);
}
void build(int p, int l, int r){
if (l == r){
mx[p] = 0;
return ;
}
int mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
pushup(p);
}
void modify(int p, int l, int r, int s, int v){
if (l == r){
mx[p] = max(mx[p], v);
return ;
}
int mid = (l + r) >> 1;
if (s <= mid)
modify(p << 1, l, mid, s, v);
else
modify(p << 1 | 1, mid + 1, r, s, v);
pushup(p);
}
int query(int p, int l, int r, int s, int t){
if (s > t)
return 0;
if (l >= s && r <= t)
return mx[p];
int mid = (l + r) >> 1;
int ans = 0xc0c0c0c0c0c0c0c0;
if (s <= mid)
ans = max(ans, query(p << 1, l, mid, s, t));
if (t > mid)
ans = max(ans, query(p << 1 | 1, mid + 1, r, s, t));
return ans;
}
}T;
int f[K][N], lg[N];
int got(int l, int r){
int k = lg[r - l + 1];
return max(f[k][l], f[k][r - (1 << k) + 1]);
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i < n; i++){
int u, v;
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
cin >> q;
for (int i = 1; i <= q; i++){
int l, r, k;
cin >> l >> r >> k;
qk[k].push_back({l, r, i});
qr[r].push_back({l, k, i});
}
dfs1(1, 0);
dfs2(1, 1);
for (int i = 1; i < n; i++){
a[i] = de[LCA(i, i + 1)];
}
stack<int> s;
s.push(0);
for (int i = 1; i < n; i++){
while (s.size() && a[s.top()] >= a[i])
s.pop();
b[i].x = s.top() + 1;
s.push(i);
}
while (!s.empty())
s.pop();
s.push(n);
for (int i = n - 1; i; i--){
while (s.size() && a[s.top()] >= a[i])
s.pop();
b[i].y = s.top();
s.push(i);
}
for (int i = 1; i < n; i++){
b[i].v = a[i];
}
sort(b + 1, b + n, [&](node1 x, node1 y){ return x.y > y.y; });
T.build(1, 1, n);
for (int r = n, i = 1; r; r--){
while (i < n && b[i].y == r){
T.modify(1, 1, n, b[i].x, b[i].v);
i++;
}
for (auto it : qr[r]){
int l = it.l, k = it.r, id = it.d;
ans[id] = max(ans[id], T.query(1, 1, n, 1, r - k + 1));
}
}
sort(b + 1, b + n, [&](node1 x, node1 y){ return x.y - x.x + 1 > y.y - y.x + 1; });
T.build(1, 1, n);
for (int k = n, i = 1; k; k--){
while (i < n && b[i].y - b[i].x + 1 == k){
T.modify(1, 1, n, b[i].y, b[i].v);
i++;
}
for (auto it : qk[k]){
int l = it.l, r = it.r, id = it.d;
ans[id] = max(ans[id], T.query(1, 1, n, l + k - 1, r));
}
}
for (int i = 1; i <= n; i++){
f[0][i] = de[i];
if (i != 1)
lg[i] = lg[i >> 1] + 1;
}
for (int j = 1; j < K; j++){
for (int i = 1; i + (1 << j - 1) <= n; i++){
f[j][i] = max(f[j - 1][i], f[j - 1][i + (1 << j - 1)]);
}
}
for (auto it : qk[1]){
int l = it.l, r = it.r, id = it.d;
ans[id] = got(l, r);
}
for (int i = 1; i <= q; i++){
cout << ans[i] << '\n';
}
}
ber,洛谷一直 waiting 是何意味啊?
LG9447 [ICPC 2021 WF] Spider Walk
原题链接:[ICPC 2021 WF] Spider Walk
分析
大概是S组复赛之前,国庆节前后aoao还没完全停课的时候,给我推的题,现在才写,纯属太菜。
由于后面的桥会影响之前的结果,所以不妨按照离中心的距离从大到小排序。
走桥,相当于交换目标射线与当前射线的编号,显然有一种可以从前往后转移的感觉,所以,我们设 d p i , j dp_{i,j} dpi,j 表示考虑了前 i i i 条给定线段,当前 j j j 射线终点为 s s s 的最小加入数量。
转移,先交换 f i − 1 , t i , f i − 1 , t i m o d n + 1 f_{i-1,t_i},f_{i-1,t_i\mod n+1} fi−1,ti,fi−1,timodn+1。考虑新线段的加入,有 f i , j ′ = min f i − 1 , k + min ( ∣ j − k ∣ , n − ∣ j − k ∣ ) f_{i,j}'=\min f_{i-1,k}+\min(|j-k|,n-|j-k|) fi,j′=minfi−1,k+min(∣j−k∣,n−∣j−k∣)。由于只有两个位置变化,尝试较小值向外和较大值优化,复杂度来到 O ( m n ) O(mn) O(mn)。