【做题笔记】2024暑假集训做题笔记
1. dp 专题#
1.1 状压dp#
P2396 yyy loves Maths VII#
设
预处理
发现每一次只需要
时间复杂度
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
#define mod 1000000007
using namespace std;
const ll N = 24, M = (1<<N)+1;
int n, m, b[3], f[M];
ll dis[M];
inline int lb(int x) {
return x & -x;
}
inline void solve(int S, int x) {
for (register int k = x, nS = S; k; nS -= k, k = (nS & -nS)){
f[S] = (f[S] + f[k ^ S] % mod) % mod;
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
for (int i = 0; i < n; i++) cin >> dis[(1 << i)];
cin >> m;
for (int i = 0; i < m; i++) cin >> b[i];
f[0] = 1;
for (ll S = 1; S < (1ll << n); S++) {
int i = S & -S, nS = S ^ i;
dis[S] = dis[nS] + dis[i];
if(dis[S] == b[0] || dis[S] == b[1]) continue ;
solve(S, i);
}
cout << f[(1<<n)-1] << '\n';
return 0;
}
1.2 换根dp#
P2986 [USACO10MAR] Great Cow Gathering G#
设
然后设
而转移仅对父节点与子节点有影响,考虑换根。
将
则有转移
取最小值即可,时间复杂度
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
#define inf 0x3f3f3f3f
using namespace std;
const int N = 1e5 + 10;
struct Node {
int v, w, nx;
} e[N << 1];
int n, a[N], h[N], tot, d[N], f[N], siz[N], ans = inf, Sum;
void add(int u, int v, int w) {
e[++tot] = (Node){v, w, h[u]};
h[u] = tot;
}
void dfs(int x, int fa) {
siz[x] = a[x];
for (int i = h[x]; i; i = e[i].nx) {
int y = e[i].v, w = e[i].w;
if(y == fa) continue;
dfs(y, x);
siz[x] += siz[y];
d[x] += d[y] + siz[y] * w;
}
}
void dfs1(int x, int fa) {
for (int i = h[x]; i; i = e[i].nx) {
int y = e[i].v, w = e[i].w;
if(y == fa) continue;
f[y] = 1ll * f[x] - siz[y] * w + (Sum - siz[y]) * w;
ans = min(ans, f[y]);
dfs1(y, x);
}
}
signed main() {
cin >> n;
For(i,1,n) cin >> a[i], Sum += a[i];
For(i,1,n-1) {
int u, v, w;
cin >> u >> v >> w;
add(u, v, w);
add(v, u, w);
}
dfs(1, 0);
f[1] = d[1];
// ans = d[1];
dfs1(1, 0);
cout << ans << '\n';
return 0;
}
2. 数据结构#
2.1 分块#
LOJ6277. 数列分块入门 1#
设
然后散块暴力加,整块打标记。查答案时直接将标记和与暴力加过的
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 5e4 + 10;
int n, L, p[N], a[N], add[N];
void upd(int l, int r, int k) {
if(p[l] == p[r]) {
For(i,l,r) a[i] += k;
return ;
}
For(i,p[l]+1,p[r]-1) add[i] += k;
for (int i = l; p[l] == p[i]; ++i) a[i] += k;
for (int i = r; p[r] == p[i]; --i) a[i] += k;
return ;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
L = sqrt(n);
For(i,1,n) cin >> a[i];
For(i,1,n) p[i] = (i-1) / L + 1;
For(i,1,n) {
int op, l, r, c;
cin >> op >> l >> r >> c;
if(op == 0) {
upd(l, r, c);
} else {
cout << a[r] + add[p[r]] << '\n';
}
}
return 0;
}
LOJ6278. 数列分块入门 2#
区间小于
然后散块暴力,整块二分求值即可。
但是二分先要排序,由于区间加,可能一个散块内修改后的顺序会被打乱。这时只需要直接重构即可。
先预处理块内的排序,修改到散块再重构,最后暴力+二分统计答案。
应常数吸吸O2才可过。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 5e4 + 10, M = 250;
int n, a[N], L, p[N], add[N], Maxp, LL[N], RR[N];
vector<int> b[M];
void reset(int k) {
vector<int>().swap(b[k]);
For(i,LL[k],RR[k]) b[k].push_back(a[i]);
sort(b[k].begin(), b[k].end());
return ;
}
void upd(int l, int r, int k) {
if(p[l] == p[r]) {
For(i,l,r) a[i] += k;
reset(p[l]);
return ;
}
For(i,p[l]+1,p[r]-1) add[i] += k;
for (int i = l; p[i] == p[l]; ++i) a[i] += k;
reset(p[l]);
for (int i = r; p[i] == p[r]; --i) a[i] += k;
reset(p[r]);
return ;
}
int ask(int l, int r, int k) {
int ans = 0; k *= k;
if(p[l] == p[r]) {
For(i,l,r) ans += (a[i] + add[p[l]] < k);
return ans;
}
For(i,p[l]+1,p[r]-1) {
ans += lower_bound(b[i].begin(), b[i].end(), k - add[i]) - b[i].begin();
}
for (int i = l; p[i] == p[l]; ++i) ans += (a[i] + add[p[i]] < k);
for (int i = r; p[i] == p[r]; --i) ans += (a[i] + add[p[i]] < k);
return ans;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
L = sqrt(n);
For(i,1,n) cin >> a[i];
For(i,1,n) {
p[i] = (i-1)/L+1; Maxp = p[i];
b[p[i]].push_back(a[i]);
}
For(i,1,Maxp) {
LL[i] = RR[i-1] + 1;
RR[i] = min(n, LL[i] + L - 1);
sort(b[i].begin(), b[i].end());
}
For(i,1,n) {
int op, l, r, c;
cin >> op >> l >> r >> c;
if(op == 0) {
upd(l, r, c);
} else {
cout << ask(l, r, c) << '\n';
}
}
return 0;
}
LOJ6279. 数列分块入门 3#
思路与数列分块入门 2差不太多,都是排序之后散块暴力统计,整块二分统计。
同样遇到修改散块就重构一下。
注意:由于是前驱,所以位置为 lower_bound 的位置减一。若搜到了第一个位置,则没有前驱,直接跳过即可。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 1e5 + 10, M = 320;
int n, L, a[N], p[N], add[N], LL[N], RR[N], Maxp;
vector<int> v[N];
void reset(int k) {
vector<int>().swap(v[k]);
For(i,LL[k],RR[k]) v[k].push_back(a[i]);
sort(v[k].begin(), v[k].end());
return ;
}
void upd(int l, int r, int k) {
if(p[l] == p[r]) {
For(i,l,r) a[i] += k;
reset(p[l]);
return ;
}
For(i,p[l]+1,p[r]-1) add[i] += k;
for (int i = l; p[l] == p[i]; ++i) a[i] += k;
reset(p[l]);
for (int i = r; p[r] == p[i]; --i) a[i] += k;
reset(p[r]);
return ;
}
int ask(int l, int r, int k) {
int ans = INT_MIN;
if(p[l] == p[r]) {
For(i,l,r) if(a[i] + add[p[l]] < k) ans = max(ans, a[i] + add[p[l]]);
return (ans == INT_MIN ? -1 : ans);
}
For(i,p[l]+1,p[r]-1) {
auto it = lower_bound(v[i].begin(), v[i].end(), k - add[i]);
if(it == v[i].begin()) continue ;
it--;
ans = max(ans, *(it) + add[i]);
}
for (int i = l; p[l] == p[i]; ++i) if(a[i] + add[p[l]] < k) ans = max(ans, a[i] + add[p[l]]);
for (int i = r; p[r] == p[i]; --i) if(a[i] + add[p[r]] < k) ans = max(ans, a[i] + add[p[r]]);
return (ans == INT_MIN ? -1 : ans);
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
L = sqrt(n);
For(i,1,n) cin >> a[i];
For(i,1,n) {
p[i] = (i-1)/L+1;
Maxp = p[i];
v[p[i]].push_back(a[i]);
}
For(i,1,Maxp) {
LL[i] = RR[i-1] + 1;
RR[i] = min(n, LL[i] + L - 1);
sort(v[i].begin(), v[i].end());
}
For(i,1,n) {
int op, l, r, c;
cin >> op >> l >> r >> c;
if(op == 0) {
upd(l, r, c);
} else {
cout << ask(l, r, c) << '\n';
}
}
return 0;
}
LOJ6280. 数列分块入门 4#
记一个
最后查询时散块直接加并加上标记,整块加
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 5e4 + 10;
int n, L, p[N], sum[N], a[N], add[N];
void upd(int l, int r, int k) {
if(p[l] == p[r]) {
For(i,l,r) {
a[i] += k;
sum[p[l]] += k;
}
return ;
}
For(i,p[l]+1,p[r]-1) add[i] += k, sum[i] += k * L;
for (int i = l; p[i] == p[l]; i++) {
a[i] += k;
sum[p[l]] += k;
}
for (int i = r; p[i] == p[r]; i--) {
a[i] += k;
sum[p[r]] += k;
}
return ;
}
int ask(int l, int r, int c) {
c++; int ans = 0;
if(p[l] == p[r]) {
For(i,l,r) ans = (ans + a[i] % c + add[p[l]] % c) % c;
return ans % c;
}
For(i,p[l]+1,p[r]-1) ans = (ans + sum[i] % c) % c;
for (int i = l; p[i] == p[l]; i++) ans = (ans + a[i] % c + add[p[l]] % c) % c;
for (int i = r; p[i] == p[r]; i--) ans = (ans + a[i] % c + add[p[r]] % c) % c;
return ans % c;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
L = sqrt(n);
For(i,1,n) cin >> a[i], p[i] = (i-1)/L+1;
For(i,1,n) sum[p[i]] += a[i];
For(i,1,n) {
int op, l, r, c;
cin >> op >> l >> r >> c;
if(op == 0) {
upd(l, r, c);
} else {
cout << ask(l, r, c) << '\n';
}
}
return 0;
}
LOJ6281. 数列分块入门 5#
考虑到对小于等于
于是对于散块暴力修改,整块若有大于
然后每次整块开完根后检查是否全部小于等于
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 5e4 + 10;
int n, a[N], sum[N], b[N], p[N], L, LL[N], RR[N], Maxp;
void reset(int k) {
sum[k] = 0;
bool f = 1;
For(i,LL[k],RR[k]) {
a[i] = sqrt(a[i]);
f &= (a[i] <= 1);
sum[k] += a[i];
}
b[k] = f;
return ;
}
void upd(int l, int r) {
if(p[l] == p[r]) {
For(i,l,r) {
sum[p[l]] -= a[i];
a[i] = sqrt(a[i]);
sum[p[l]] += a[i];
}
return ;
}
For(i,p[l]+1,p[r]-1) {
if(!b[i]) reset(i);
}
for (int i = l; p[i] == p[l]; ++i) {
sum[p[l]] -= a[i];
a[i] = sqrt(a[i]);
sum[p[l]] += a[i];
}
for (int i = r; p[i] == p[r]; --i) {
sum[p[r]] -= a[i];
a[i] = sqrt(a[i]);
sum[p[r]] += a[i];
}
return ;
}
int ask(int l, int r) {
int ans = 0;
if(p[l] == p[r]) {
For(i,l,r) ans += a[i];
return ans;
}
For(i,p[l]+1,p[r]-1) ans += sum[i];
for (int i = l; p[i] == p[l]; ++i) ans += a[i];
for (int i = r; p[i] == p[r]; --i) ans += a[i];
return ans;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
L = sqrt(n);
For(i,1,n) cin >> a[i];
For(i,1,n) p[i] = (i-1)/L+1, Maxp = p[i], sum[p[i]] += a[i];
For(i,1,Maxp) {
LL[i] = RR[i-1] + 1;
RR[i] = min(n, LL[i] + L - 1);
}
For(i,1,n) {
int op, l, r, c;
cin >> op >> l >> r >> c;
if(op == 0) {
upd(l, r);
} else {
cout << ask(l, r) << '\n';
}
}
return 0;
}
LOJ6283. 数列分块入门 7#
与线段数2小像...
维护
散块直接暴力可能会遇到一些问题,就是块内的元素标记还没传下来算完,就加/乘了。答案显然不正确。于是散块先要暴力下传标记,后暴力加/乘即可。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
#define mod 10007
using namespace std;
const int N = 1e5 + 10;
int n, a[N], p[N], mul[N], add[N], LL[N], RR[N], L, Maxp;
void reset(int k) {
For(i,LL[k],RR[k]) {
a[i] *= mul[k]; a[i] %= mod;
a[i] += add[k]; a[i] %= mod;
}
mul[k] = 1, add[k] = 0;
return ;
}
void updd(int l, int r, int c) {
c %= mod;
if(p[l] == p[r]) {
reset(p[l]);//
For(i,l,r) a[i] = (a[i] + c) % mod;
return ;
}
For(i,p[l]+1,p[r]-1) add[i] = (add[i] + c) % mod;
reset(p[l]);//
for (int i = l; p[i] == p[l]; ++i) a[i] = (a[i] + c) % mod;
reset(p[r]);//
for (int i = r; p[i] == p[r]; --i) a[i] = (a[i] + c) % mod;
return ;
}
void updm(int l, int r, int c) {
c %= mod;
if(p[l] == p[r]) {
reset(p[l]);//
For(i,l,r) a[i] = (a[i] * c) % mod;
return ;
}
For(i,p[l]+1,p[r]-1) add[i] = (add[i] * c) % mod, mul[i] = (mul[i] * c) % mod;
reset(p[l]);//
for (int i = l; p[i] == p[l]; ++i) a[i] = (a[i] * c) % mod;
reset(p[r]);//
for (int i = r; p[i] == p[r]; --i) a[i] = (a[i] * c) % mod;
return ;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
L = sqrt(n);
For(i,1,n) cin >> a[i], p[i] = (i-1)/L+1, Maxp = p[i];
For(i,1,Maxp) {
LL[i] = RR[i-1] + 1;
RR[i] = min(n, LL[i] + L - 1);
mul[i] = 1; add[i] = 0;
}
For(i,1,n) {
int op, l, r, c;
cin >> op >> l >> r >> c;
if(op == 0) {
updd(l, r, c);
} else if(op == 1) {
updm(l, r, c);
} else {
reset(p[r]);
cout << a[r] << '\n';
}
}
return 0;
}
/*
S * mul + add
*/
luoguP5356 [Ynoi2017] 由乃打扑克 #
发现第
发现瓶颈在我们不知道第
而枚举又太劣,发现这玩意有单调性,直接二分即可。
但是写完发现过不了被卡,因为这是由乃OI。
二分值域优化:每一次修改操作最多让值域扩大
块内最值优化:如果块内的最小值都比
块长优化:调整块长为
于是愉快的 AC 了!
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
#define inf 2000000000
using namespace std;
const int N = 1e5 + 10, M = 505;
int n, m, a[N], L, LL[N], RR[N], p[N], add[N], Maxp, Max[N], Min[N], cnt = 1;
vector<int> b[N];
void reset(int k) {
vector<int>().swap(b[k]);
For(i,LL[k],RR[k]) b[k].push_back(a[i]);
sort(b[k].begin(), b[k].end());
Min[k] = b[k][0], Max[k] = b[k][b[k].size()-1];
return ;
}
void upd(int l, int r, int k) {
if(p[l] == p[r]) {
For(i,l,r) a[i] += k;
reset(p[l]);
return ;
}
For(i,p[l]+1,p[r]-1) add[i] += k;
for (int i = l; p[i] == p[l]; ++i) a[i] += k;
reset(p[l]);
for (int i = r; p[i] == p[r]; --i) a[i] += k;
reset(p[r]);
return ;
}
int ask(int l, int r, int k) {
int ans = 0;
if(p[l] == p[r]) {
For(i,l,r) ans += (a[i] + add[p[l]] < k);
return ans;
}
For(i,p[l]+1,p[r]-1) {
if(Max[i] + add[i] < k) {
ans += L; continue;
}
if(Min[i] + add[i] > k) {
continue;
}
ans += lower_bound(b[i].begin(), b[i].end(), k - add[i]) - b[i].begin();
}
for (int i = l; p[i] == p[l]; ++i) ans += (a[i] + add[p[i]] < k);
for (int i = r; p[i] == p[r]; --i) ans += (a[i] + add[p[i]] < k);
return ans;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
L = 90;
For(i,1,n) cin >> a[i];
For(i,1,n) {
p[i] = (i-1)/L+1; Maxp = p[i];
b[p[i]].push_back(a[i]);
}
For(i,1,Maxp) {
LL[i] = RR[i-1] + 1;
RR[i] = min(n, LL[i] + L - 1);
sort(b[i].begin(), b[i].end());
Min[i] = b[i][0], Max[i] = b[i][b[i].size()-1];
}
while(m--) {
int op, l, r, k;
cin >> op >> l >> r >> k;
if(op == 1) {
if(r - l + 1 < k) {
puts("-1"); continue;
}
int x = -2e4 * cnt - 5000, y = -x;
while(x <= y) {
int mid = x + y >> 1;
if(ask(l, r, mid) < k) x = mid + 1;
else y = mid - 1;
}
cout << x - 1 << '\n';
} else {
upd(l, r, k);
cnt++;
}
}
return 0;
}
luoguP2801 教主的魔法#
其实就是 LOJ6278. 数列分块入门 2 中的
然后再加上块内最值优化与块长优化(块长调为
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 1e6 + 5, M = 1e3 + 5;
int n, q, a[N], L, p[N], add[N], Maxp, Max[N], Min[N], LL[N], RR[N];
vector<int> b[N];
void reset(int k) {
vector<int>().swap(b[k]);
For(i,LL[k],RR[k]) b[k].push_back(a[i]);
sort(b[k].begin(), b[k].end());
Max[k] = b[k][b[k].size()-1], Min[k] = b[k][0];
return ;
}
void upd(int l, int r, int k) {
if(p[l] == p[r]) {
For(i,l,r) a[i] += k;
reset(p[l]);
return ;
}
For(i,p[l]+1,p[r]-1) add[i] += k;
for (int i = l; p[i] == p[l]; ++i) a[i] += k;
reset(p[l]);
for (int i = r; p[i] == p[r]; --i) a[i] += k;
reset(p[r]);
return ;
}
int ask(int l, int r, int k) {
int ans = 0;
if(p[l] == p[r]) {
For(i,l,r) ans += (a[i] + add[p[l]] >= k);
return ans;
}
For(i,p[l]+1,p[r]-1) {
if(Min[i] + add[i] >= k) {
ans += L; continue;
}
if(Max[i] + add[i] < k) continue;
int x = lower_bound(b[i].begin(), b[i].end(), k - add[i]) - b[i].begin();
ans += L - x;
}
for (int i = l; p[i] == p[l]; ++i) ans += (a[i] + add[p[i]] >= k);
for (int i = r; p[i] == p[r]; --i) ans += (a[i] + add[p[i]] >= k);
return ans;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> q;
L = 850;
For(i,1,n) cin >> a[i];
For(i,1,n) {
p[i] = (i-1)/L+1; Maxp = p[i];
b[p[i]].push_back(a[i]);
}
For(i,1,Maxp) {
LL[i] = RR[i-1] + 1;
RR[i] = min(n, LL[i] + L - 1);
sort(b[i].begin(), b[i].end());
Max[i] = b[i][b[i].size()-1], Min[i] = b[i][0];
}
while(q--) {
int l, r, c;
char op;
cin >> op >> l >> r >> c;
if(op == 'M') {
upd(l, r, c);
} else {
cout << ask(l, r, c) << '\n';
}
}
return 0;
}
2.2 平衡树#
luoguP3369【模板】普通平衡树 #
平衡树模板,现在只会 treap(有旋的忘了)
可以参考yzy的学习笔记【算法】平衡树
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define H 19260817
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define MOD 1000003
#define mod 1000000007
using namespace std;
inline int read() {
rint x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
void print(int x){
if(x<0){putchar('-');x=-x;}
if(x>9){print(x/10);putchar(x%10+'0');}
else putchar(x+'0');
return;
}
const int N = 1e5 + 10;
struct Node {
int l, r, val, key, siz;
} t[N];
int n, cnt, root;
mt19937 rd(114514);
int Mknode(int val) {//建立新节点
t[++cnt].val = val;
t[cnt].key = rd();
t[cnt].siz = 1;
return cnt;
}
void pushup(int p) {//上传信息
t[p].siz = t[t[p].l].siz + t[t[p].r].siz + 1;
return ;
}
void split(int p, int val, int &x, int &y) {
if(!p) {
x = y = 0;
return ;
}
if(t[p].val <= val) {
x = p;
split(t[p].r, val, t[p].r, y);
} else {
y = p;
split(t[p].l, val, x, t[p].l);
}
pushup(p);
}
int merge(int x, int y) {
if(!x || !y) return x + y;
if(t[x].key > t[y].key) {
t[x].r = merge(t[x].r, y);
pushup(x);
return x;
} else {
t[y].l = merge(x, t[y].l);
pushup(y);
return y;
}
}
int x, y, z;
void Ins(int val) {
split(root, val, x, y);
root = merge(merge(x, Mknode(val)), y);
}
void Del(int val) {
split(root, val, x, z);
split(x, val-1, x, y);
y = merge(t[y].l, t[y].r);
root = merge(merge(x, y), z) ;
}
int rk(int val) {
split(root, val-1, x, y);
int ans = t[x].siz + 1;
root = merge(x, y);
return ans;
}
int kth(int x) {
int p = root;
while(p) {
if(t[t[p].l].siz + 1 == x) return t[p].val;
else if(t[t[p].l].siz >= x) p = t[p].l;
else x -= (t[t[p].l].siz + 1), p = t[p].r;
}
}
int pre(int val) {
split(root, val-1, x, y);
int p = x;
while(t[p].r) p = t[p].r;
int ans = t[p].val;
root = merge(x, y);
return ans;
}
int nxt(int val) {
split(root, val, x, y);
int p = y;
while(t[p].l) p = t[p].l;
int ans = t[p].val;
root = merge(x, y);
return ans;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
while(n--) {
int op, x; cin >> op >> x;
if(op == 1) Ins(x);
else if(op == 2) Del(x);
else if(op == 3) cout << rk(x) << '\n';
else if(op == 4) cout << kth(x) << '\n';
else if(op == 5) cout << kth(rk(x) - 1) << '\n';
else cout << kth(rk(x + 1)) << '\n';
}
return 0;
}
CF799B T-shirt buying#
考虑到
由于一个物品有两种颜色,卖完后删去其中一个 set 的物品价格,但是还有一个颜色的对应的物品价格没有删去。这时候只要将物品的编号也插入 set 中,然后延迟删除即可。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define val first
#define id second
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
#define inf 1000000010
using namespace std;
const int N = 2e5 + 10;
int n, m, p[N], a[N], b[N];
multiset<pair<int, int> > s[4];
map<int, bool> mp;
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
For(i,1,n) cin >> p[i];
For(i,1,n) cin >> a[i];
For(i,1,n) cin >> b[i];
For(i,1,n) {
s[a[i]].insert({p[i], i});
s[b[i]].insert({p[i], i});
}
cin >> m;
while(m--) {
int x; cin >> x;
if(!s[x].size()) cout << "-1 ";
else {
pair<int, int> P = *s[x].begin();
while(mp[P.id]) {
s[x].erase(s[x].begin());
if(!s[x].size()) {
cout << "-1 ";
goto yzy;
}
P = *s[x].begin();
}
cout << P.first << ' ';
mp[P.id] = 1;
s[x].erase(s[x].begin());
yzy: ;
}
}
return 0;
}
2.3 KD-tree#
luoguP4148 简单题#
KD-tree 模板,参考yzy的学习笔记 【算法】KD-tree。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N = 2e5 + 10, LG = 21;
struct KD_tree {
int x[2], v;
int l, r, sum;
int L[2], R[2];
} t[N], lf, rh;
int n, rt[30], b[N], cnt;
void pushup(int p) {
t[p].sum = t[t[p].l].sum + t[t[p].r].sum + t[p].v;
for (int k : {0, 1}) {
t[p].R[k] = t[p].L[k] = t[p].x[k];
if(t[p].l) {
t[p].L[k] = min(t[p].L[k], t[t[p].l].L[k]);
t[p].R[k] = max(t[p].R[k], t[t[p].l].R[k]);
}
if(t[p].r) {
t[p].L[k] = min(t[p].L[k], t[t[p].r].L[k]);
t[p].R[k] = max(t[p].R[k], t[t[p].r].R[k]);
}
}
}
int build(int l, int r, int D = 0) {
int mid = l + r >> 1;
nth_element(b + l, b + mid, b + r + 1, [D](int x, int y){return t[x].x[D] < t[y].x[D];});
int x = b[mid];
if(l < mid) t[x].l = build(l, mid - 1, D ^ 1);
if(r > mid) t[x].r = build(mid + 1, r, D ^ 1);
pushup(x);
return x;
}
int query(int p) {
if(!p) return 0;
bool f = 1; int ans = 0;
for (int k : {0, 1}) {
f &= (t[p].L[k] >= lf.x[k] && t[p].R[k] <= rh.x[k]);
}
if(f) return t[p].sum;
for (int k : {0, 1}) {
if(t[p].R[k] < lf.x[k] || t[p].L[k] > rh.x[k]) return 0;
}
f = 1;
for (int k : {0, 1}) {
f &= (lf.x[k] <= t[p].x[k] && t[p].x[k] <= rh.x[k]);
}
if(f) ans = t[p].v;
return ans + query(t[p].l) + query(t[p].r);
}
void Add(int &p) {
if(!p) return ;
b[++cnt] = p;
Add(t[p].l);
Add(t[p].r);
p = 0;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
n = 0; int lst = 0;
while(1) {
int op; cin >> op;
if(op == 3) break;
else if(op == 1) {
int x, y, w; cin >> x >> y >> w;
x ^= lst, y ^= lst, w ^= lst;
t[++n] = {{x, y}, w};
b[cnt=1] = n;
for (int siz = 0; ; siz++) {
if(!rt[siz]) {
rt[siz] = build(1, cnt);
break;
} else {
Add(rt[siz]);
}
}
} else {
cin >> lf.x[0] >> lf.x[1] >> rh.x[0] >> rh.x[1];
lf.x[0] ^= lst;
lf.x[1] ^= lst;
rh.x[0] ^= lst;
rh.x[1] ^= lst;
lst = 0;
For(i,0,LG) lst += query(rt[i]);
cout << lst << '\n';
}
}
return 0;
}
/*
4
1 2 3 3
2 1 1 3 3
1 1 1 1
2 1 1 0 7
3
*/
2.4 线段树#
P2633 Count on a tree#
区间的第
考虑主席树类似于前缀和做维护,每一次可持久化父结点的线段树信息,并记录该结点本身的信息。
询问时只要求出 LCA,然后根据公式
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
#define inf 2147483949
using namespace std;
const int N = 5e5 + 10, M = 1e7 + 10;
struct Node {
int v, nx;
} e[N << 1];
struct Seg {
int ls, rs, val;
} t[M];
int n, m, h[N], dep[N], anc[N][21], a[N], tot, root[N], fa[N], idx, lst;
void add(int u, int v) {
e[++tot] = (Node){v, h[u]};
h[u] = tot;
}
void pushup(int p) {
t[p].val = t[t[p].ls].val + t[t[p].rs].val;
}
void upd(int la, int &p, int L, int R, int x) {
p = ++idx;
t[p] = t[la];
if(L == R) {
t[p].val++;
return ;
}
int mid = L + R >> 1;
if(x <= mid) upd(t[la].ls, t[p].ls, L, mid, x);
else upd(t[la].rs, t[p].rs, mid + 1, R, x);
pushup(p);
}
int ask(int x, int y, int z, int l, int L, int R, int k) {
if(L == R) return L;
int K = t[t[x].ls].val + t[t[y].ls].val - t[t[z].ls].val - t[t[l].ls].val;
int mid = L + R >> 1;
if(k <= K) return ask(t[x].ls, t[y].ls, t[z].ls, t[l].ls, L, mid, k);
else return ask(t[x].rs, t[y].rs, t[z].rs, t[l].rs, mid + 1, R, k - K);
}
void dfs(int x, int f) {
dep[x] = dep[f] + 1;
fa[x] = f;
upd(root[f], root[x], 1, inf, a[x]);
for (int i = h[x]; i; i = e[i].nx) {
int y = e[i].v;
if(y == f) continue;
anc[y][0] = x;
dfs(y, x);
}
}
void init() {
For(j,1,19) {
For(i,1,n) {
anc[i][j] = anc[anc[i][j-1]][j-1];
}
}
}
int lca(int x, int y) {
if(dep[x] < dep[y]) swap(x, y);
FOR(i,19,0) if(dep[anc[x][i]] >= dep[y]) x = anc[x][i];
if(x == y) return x;
FOR(i,19,0) if(anc[x][i] != anc[y][i]) x = anc[x][i], y = anc[y][i];
return anc[x][0];
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
For(i,1,n) cin >> a[i];
For(i,1,n-1) {
int u, v;
cin >> u >> v;
add(u, v); add(v, u);
}
dfs(1, 0);
init();
while(m--) {
int u, v, k;
cin >> u >> v >> k;
u ^= lst;
int LCA = lca(u, v);
lst = ask(root[u], root[v], root[LCA], root[fa[LCA]], 1, inf, k);
cout << lst << '\n';
}
return 0;
}
3. 图论专题#
3.1 网络流#
luogu1402 酒店之王#
考虑按怎样的顺序建模。
如果按照“人, 房间, 菜”的顺序建模,就有可能出现一个人选择多个房间的情况。
显然可以发现把人这一类放在中间进行建模是最好的,按照“房间,人 , 菜”的顺序,依据人的喜好连边。
但是注意每一个人只能选择一个房间和一道菜。所以要将人结点拆开分成两个点。中间连一条
最后跑最大流即为答案。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define For(i,l,r) for(int i=l;i<=r;++i)
#define FOR(i,r,l) for(int i=r;i>=l;--i)
#define inf 10000000000
using namespace std;
const int N = 1e5 + 10;
struct Node {
int v, nx, w;
} e[N << 1];
int n, p, q, h[N], tot = 1, s, t, dep[N], cur[N];
void add(int u, int v, int w) {
e[++tot] = (Node){v, h[u], w};
h[u] = tot;
}
bool bfs() {
For(i,1,n) dep[i] = 0;
queue<int> q;
q.push(s);
dep[s] = 1;
while(!q.empty()) {
int x = q.front();
q.pop();
for (int i = h[x]; i; i = e[i].nx) {
int y = e[i].v;
if(e[i].w > 0 && !dep[y]) {
dep[y] = dep[x] + 1;
q.push(y);
}
}
}
return dep[t] != 0;
}
int dfs(int x, int flow) {
int res = 0;
if(x == t) return flow;
for (int &i = cur[x]; i; i = e[i].nx) {
int y = e[i].v;
if(e[i].w > 0 && dep[y] == dep[x] + 1) {
int k = dfs(y, min(flow - res, e[i].w));
res += k, e[i].w -= k, e[i ^ 1].w += k;
if(res == flow) return res;
}
}
return res;
}
int Dinic() {
int ans = 0;
while(bfs()) {
memcpy(cur, h, sizeof cur);
ans += dfs(s, inf);
}
return ans;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> p >> q;
For(i,1,n) {
add(i, i + n, 1);
add(i + n, i, 0);
}
int id = 2 * n;
For(i,1,n) {
For(j,1,p) {
int op; cin >> op;
if(op) {
add(id + j, i, 1);
add(i, id + j, 0);
}
}
}
id += p;
For(i,1,n) {
For(j,1,q) {
int op; cin >> op;
if(op) {
add(i + n, id + j, 1);
add(id + j, i + n, 0);
}
}
}
id += q;
s = id + 1, t = id + 2;
For(i, 2 * n + 1, 2 * n + p) {
add(s, i, 1); add(i, s, 0);
}
For(i, 2 * n + p + 1, 2 * n + p + q) {
add(i, t, 1); add(t, i, 0);
}
n = id + 2;
cout << Dinic() << '\n';
return 0;
}
注意一下结点的编号即可。双倍经验
作者:Daniel-yao
出处:https://www.cnblogs.com/Daniel-yao/p/18269170
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】