三维的空间中有 n n n 个元素,初始时每个空间元素均为0。更新操作是0变1,1变0,是一个立方体型区域内的所有元素都更新。然后查询是问某个点的元素是0还是1. ( 1 < = n < = 100 , m < = 10000 1<=n<=100, m <=10000 1<=n<=100,m<=10000)
三维树状数组,维护三维差分数组,所有加法减法都是异或,就可以了。
对于 n n n 维差分或者是 n n n 维前缀和,公式一定有 2 n 2^n 2n 项,根据 x 2 , y 2 , z 2 . . . x_2, y_2,z_2... x2,y2,z2... 这些东西出现次数的奇偶性就可以判断出来符号了.
#include
using namespace std;
const int N = 110;
int tr[N][N][N];
int n, m;
int lowbit(int x)
{
return x & -x;
}
void add(int x, int y, int z)
{
for(int i = x; i <= n; i += lowbit(i))
{
for(int j = y; j <= n; j += lowbit(j))
{
for(int k = z; k <= n; k += lowbit(k))
{
tr[i][j][k] ^= 1;
}
}
}
}
int sum(int x, int y, int z)
{
int res = 0;
for(int i = x; i; i -= lowbit(i))
{
for(int j = y; j; j -= lowbit(j))
{
for(int k = z; k; k -= lowbit(k))
{
res ^= tr[i][j][k];
}
}
}
return res;
}
void modify(int x1, int y1, int z1, int x2, int y2, int z2)
{
add(x1, y1, z1);
add(x1, y1, z2 + 1);
add(x1, y2 + 1, z1);
add(x1, y2 + 1, z2 + 1);
add(x2 + 1, y1, z1);
add(x2 + 1, y1, z2 + 1);
add(x2 + 1, y2 + 1, z1);
add(x2 + 1, y2 + 1, z2 + 1);
}
int main()
{
while(cin >> n >> m)
{
memset(tr, 0, sizeof tr);
int op, x1, y1, z1, x2, y2, z2;
for(int i = 1; i <= m; i++)
{
scanf("%d", &op);
if(!op)
{
scanf("%d%d%d", &x1, &y1, &z1);
printf("%d\n", sum(x1, y1, z1));
}
else
{
scanf("%d%d%d%d%d%d", &x1, &y1, &z1, &x2, &y2, &z2);
modify(x1, y1, z1, x2, y2, z2);
}
}
}
return 0;
}
长度为 n ( n ≤ 1 0 5 ) n(n \le 10^5) n(n≤105) 的序列,找到有多少个连续区间,满足区间内的最大值减最小值小于 k k k
这种需要枚举所有连续子序列的问题,基本都是暴力枚举一个端点,然后看另一个端点有多少个是满足的. 固定一个端点之后,区间的极差一定是单调的,因此二分就可以了.
其实现在看来只需要双指针就可以了
注意这个题是多组输入,因此求 s t st st 表的时候,要小心不要超过 n n n 的边界.
#include
using namespace std;
const int N = 100010, M = 18;
int lg[N], a[N], n, k;
int Fmax[N][M], Fmin[N][M];
typedef long long ll;
void pre()
{
for(int j = 0; j < M; j++)
{
for(int i = 1; i <= n; i++)
{
if(!j) Fmin[i][j] = Fmax[i][j] = a[i];
else if(i + (1 << (j - 1)) <= n)
{
Fmax[i][j] = max(Fmax[i][j - 1], Fmax[i + (1 << (j - 1))][j - 1]);
Fmin[i][j] = min(Fmin[i][j - 1], Fmin[i + (1 << (j - 1))][j - 1]);
}
else
{
Fmax[i][j] = Fmax[i][j - 1], Fmin[i][j] = Fmin[i][j - 1];
}
}
}
}
int query(int l, int r)
{
int len = lg[r - l + 1];
return max(Fmax[l][len], Fmax[r - (1 << len) + 1][len]) - min(Fmin[l][len], Fmin[r - (1 << len) + 1][len]);
}
int main()
{
for(int i = 1; i < N; i++) lg[i] = log2(i);
int T;
scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
pre();
ll ans = 0;
for(int i = 1; i <= n; i++)
{
int l = 1, r = i;
while(r > l)
{
int mid = (l + r) / 2;
if(query(mid, i) >= k) l = mid + 1;
else r = mid;
}
ans += (i - l + 1);
}
printf("%lld\n", ans);
}
return 0;
}
给你 n n n 个数, Q Q Q 次询问,问你 ( l , r ) (l,r) (l,r) 区间的 g c d gcd gcd 是多少,并且有多少子串的 g c d gcd gcd 和询问的 g c d gcd gcd 是相同的。
$n \le 10^5,Q \le 10^5, a_i \le 10^9 $
任意区间的 g c d gcd gcd 可以用 s t st st 表快速求得。然后我们预处理出每一个 g c d gcd gcd 有多少个。方法是这样的:暴力枚举右端点,那么前面的 g c d gcd gcd 一定是一段一段的. 那么我们可以把前面一段一段的 gcd 存下来,每次加进来一个数,前面会有若干区间的 gcd 下降. 把gcd相同的区间合并成一个区间.
小心答案爆 int.
#include
using namespace std;
const int N = 100010, M = 18;
typedef long long ll;
typedef pair<int, int> P;
#define x first
#define y second
int a[N], f[N][M], lg[N];
unordered_map<int, ll> mp;
int n, m, kase;
void pre()
{
mp.clear();
for(int j = 0; j < M; j++)
{
for(int i = 1; (i + (1 << j) - 1) <= n; i++)
{
if(!j) f[i][j] = a[i];
else f[i][j] = __gcd(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);
}
}
vector<P> vec;
for(int i = 1; i <= n; i++)
{
for(auto& p : vec)
{
p.x = __gcd(p.x, a[i]);
}
vec.push_back({a[i], 1});
vector<P> tmp;
for(auto p : vec)
{
if(tmp.size() && tmp.back().x == p.x) tmp.back().y += p.y;
else tmp.push_back(p);
mp[p.x] += p.y;
}
vec = tmp;
}
}
int query(int l, int r)
{
int len = lg[r - l + 1];
return __gcd(f[l][len], f[r - (1 << len) + 1][len]);
}
int main()
{
for(int i = 1; i < N; i++) lg[i] = log2(i);
int T;
scanf("%d", &T);
while(T--)
{
scanf("%d\n", &n);
for(int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
}
pre();
printf("Case #%d:\n", ++kase);
scanf("%d", &m);
for(int i = 1; i <= m; i++)
{
int l, r;
scanf("%d%d", &l, &r);
int d = query(l, r);
printf("%d %lld\n", d, mp[d]);
}
}
return 0;
}
n ( n ≤ 1 0 5 ) n(n \le 10^5) n(n≤105) 个点 n n n 条无向边的连通图, Q ( Q ≤ 1 0 4 ) Q(Q \le 10^4) Q(Q≤104) 个询问,问两点之间的最短路是多少。
基环树,任意删掉一条边,假设是 ( a , b ) (a, b) (a,b). 若询问 u , v u, v u,v 的最小值,则答案在 d i s t ( u , v ) , d i s t ( u , a ) + w ( a , b ) + d i s t ( b , v ) dist(u, v), dist(u, a) + w(a, b) + dist(b, v) dist(u,v),dist(u,a)+w(a,b)+dist(b,v) 与 d i s t ( u , b ) + w ( a , b ) + d i s t ( a , v ) dist(u, b) + w(a, b) + dist(a, v) dist(u,b)+w(a,b)+dist(a,v) 三者取最小值.
无向图基环树,一定要记录from。不然找自环的时候,找的就是无向边的自环.(自行分析)
最近公共祖先还得再写几次,一写一堆bug
#include
using namespace std;
const int N = 100010, M = N * 2;
int h[N], e[M], ne[M], w[M], idx;
inline void add(int a, int b, int c)
{
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
int n, m;
bool st[N], ins[N];
int rmid, rma, rmb;
void dfs_c(int u, int from)
{
st[u] = ins[u] = true;
for(int i = h[u]; i != -1; i = ne[i])
{
//无向图基环树,一定要记录from。不然找自环的时候,找的就是无向边的自环.
if(i == (from ^ 1)) continue;
int v = e[i];
if(!st[v]) dfs_c(v, i);
else if(ins[v])
{
rmid = i, rma = u, rmb = v;
}
}
ins[u] = false;
}
//小心记录两个depth,一个用作lca的查询,一个用作查询到根节点的距离。因为这个树的边是有权重的.
int fa[N][18], dep[N], ndep[N];
void bfs()
{
queue<int> que;
for(int i = 1; i <= n; i++) ndep[i] = dep[i] = -1;
dep[1] = 1, ndep[1] = 0;
que.push(1);
for(int k = 0; k < 18; k++) fa[1][k] = 1;
while(que.size())
{
int u = que.front(); que.pop();
for(int i = h[u]; i != -1; i = ne[i])
{
if(i == rmid || (i ^ 1) == rmid) continue;
int v = e[i];
if(dep[v] == -1)
{
dep[v] = dep[u] + 1;
ndep[v] = ndep[u] + w[i];
fa[v][0] = u;
for(int k = 1; k < 18; k++) fa[v][k] = fa[fa[v][k - 1]][k - 1];
que.push(v);
}
}
}
}
int lca(int a, int b)
{
if(dep[a] > dep[b]) swap(a, b);
for(int k = 17; k >= 0; k--)
{
if(dep[a] <= dep[fa[b][k]]) b = fa[b][k];
}
if(a == b) return a;
for(int k = 17; k >= 0; k--)
{
if(fa[a][k] != fa[b][k]) a = fa[a][k], b = fa[b][k];
}
return fa[a][0];
}
int dist(int u, int v)
{
int anc = lca(u, v);
//printf("** u = %d dep[u] = %d, v = %d dep[v] = %d, anc = %d\n", u, dep[u], v, dep[v], anc);
return ndep[u] + ndep[v] - 2 * ndep[anc];
}
int main()
{
while(cin >> n, n)
{
for(int i = 1; i <= n; i++)
{
h[i] = -1, st[i] = false;
}
idx = 0;
for(int i = 1; i <= n; i++)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
a++, b++;
add(a, b, c), add(b, a, c);
}
dfs_c(1, -1);
bfs();
//printf("** remove id = %d, rma = %d, rmb = %d\n", rmid, rma, rmb);
scanf("%d", &m);
for(int i = 1; i <= m; i++)
{
int u, v;
scanf("%d%d", &u, &v);
u++, v++;
int ans = dist(u, v);
ans = min(dist(u, rma) + w[rmid] + dist(rmb, v), ans);
ans = min(dist(u, rmb) + w[rmid] + dist(rma, v), ans);
printf("%d\n", ans);
}
}
return 0;
}
给出一个长度为 n n n 的数组 A A A,接下来有 m m m 次操作
1 l r
:对所有区间 [ l , r ] [l,r] [l,r] 中的整数 i i i,把 A [ i ] A[i] A[i] 变成 φ ( A [ i ] ) φ(A[i]) φ(A[i]) (指欧拉函数)
2 l r x
:对所有区间 [ l , r ] [l,r] [l,r] 中的整数 i i i,把 A [ i ] A[i] A[i] 变成 x x x
3 l r
:询问 [ l , r ] [l,r] [l,r] 的区间和
n , m ( n ≤ 3 × 1 0 5 , m ≤ 3 × 1 0 5 ) n,m(n≤3×10^5,m≤3×10^5) n,m(n≤3×105,m≤3×105), 1 ≤ A [ i ] ≤ 1 0 7 1≤A[i]≤10^7 1≤A[i]≤107
保证任何时刻 1 ≤ A i ≤ 1 0 7 1 \le A_i \le 10^7 1≤Ai≤107
几个减少代码量的技巧:
#include
#define lson u << 1
#define rson u << 1 | 1
typedef unsigned int u32;
typedef int i32;
typedef unsigned long long u64;
typedef long long i64;
typedef __int128_t i128;
typedef __uint128_t u128;
using namespace std;
const int N = 300010;
const int M = 10000010;
int prime[M], cnt, phi[M];
bool st[M];
void sieve(int n)
{
phi[1] = 1;
for(int i = 2; i <= n; i++)
{
if(!st[i]) prime[cnt++] = i, phi[i] = i - 1;
for(int j = 0; prime[j] * i <= n; j++)
{
st[i * prime[j]] = true;
if(i % prime[j] == 0)
{
phi[i * prime[j]] = phi[i] * prime[j];
break;
}
phi[i * prime[j]] = phi[i] * (prime[j] - 1);
}
}
}
struct node
{
int l, r, x;
bool lazy;
u64 sum;
}tr[N * 4];
int a[N];
void pushup(int u)
{
tr[u].sum = tr[lson].sum + tr[rson].sum;
if(tr[lson].x && tr[lson].x == tr[rson].x)
{
tr[u].x = tr[lson].x;
}
else tr[u].x = 0;
}
void build(int u, int l, int r)
{
tr[u] = {l, r};
if(l == r)
{
tr[u].x = a[l];
tr[u].sum = a[l];
}
else
{
int mid = l + r >> 1;
build(lson, l, mid), build(rson, mid + 1, r);
pushup(u);
}
}
void pushdown(int u)
{
if(tr[u].lazy)
{
tr[lson].lazy = tr[rson].lazy = true;
tr[lson].x = tr[rson].x = tr[u].x;
tr[lson].sum = 1ll * tr[lson].x * (tr[lson].r - tr[lson].l + 1);
tr[rson].sum = 1ll * tr[rson].x * (tr[rson].r - tr[rson].l + 1);
}
tr[u].lazy = false;
}
void modify1(int u, int x, int L, int R)
{
if(L <= tr[u].l && tr[u].r <= R)
{
tr[u].lazy = true;
tr[u].x = x;
tr[u].sum = 1ll * x * (tr[u].r - tr[u].l + 1);
}
else
{
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(L <= mid) modify1(lson, x, L, R);
if(R > mid) modify1(rson, x, L, R);
pushup(u);
}
}
void modify2(int u, int L, int R)
{
if(L <= tr[u].l && tr[u].r <= R)
{
if(tr[u].x)
{
modify1(u, phi[tr[u].x], L, R);
}
else
{
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
modify2(lson, L, R), modify2(rson, L, R);
pushup(u);
}
}
else
{
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(L <= mid) modify2(lson, L, R);
if(R > mid) modify2(rson, L, R);
pushup(u);
}
}
u64 query(int u, int L, int R)
{
if(L <= tr[u].l && tr[u].r <= R)
{
return tr[u].sum;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
u64 res = 0;
if(L <= mid) res += query(lson, L, R);
if(R > mid) res += query(rson, L, R);
return res;
}
int main()
{
sieve(M - 1);
int T;
scanf("%d", &T);
while(T--)
{
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
build(1, 1, n);
for(int i = 1; i <= m; i++)
{
int op, l, r, x;
scanf("%d%d%d", &op, &l, &r);
if(op == 1)
{
modify2(1, l, r);
}
else if(op == 2)
{
scanf("%d", &x);
modify1(1, x, l, r);
}
else
{
printf("%llu\n", query(1, l, r));
}
}
}
return 0;
}
第一行一个整数 n n n,代表数列中数的个数。
第二行 n n n 个正整数,表示初始状态下数列中的数。
第三行一个整数 m m m,表示有 m m m 次操作。
接下来 m m m 行每行三个整数 k l r
。
[l,r]
中的每个数开平方(下取整)。[l,r]
中各个数的和。数据中有可能 l > r l>r l>r,所以遇到这种情况请交换 l l l 和 r r r。
#include
#define lson u << 1
#define rson u << 1 | 1
typedef unsigned int u32;
typedef int i32;
typedef unsigned long long u64;
typedef long long i64;
typedef __int128_t i128;
typedef __uint128_t u128;
const u32 mod = 1e9 + 7;
using namespace std;
const int N = 100010;
struct node
{
int l, r;
u64 sum, x;
}tr[N * 4];
u64 a[N];
void pushup(int u)
{
tr[u].sum = tr[lson].sum + tr[rson].sum;
if(tr[lson].x && tr[lson].x == tr[rson].x)
{
tr[u].x = tr[lson].x;
}
else tr[u].x = 0;
}
void build(int u, int l, int r)
{
tr[u] = {l, r};
if(l == r)
{
tr[u].sum = tr[u].x = a[l];
}
else
{
int mid = l + r >> 1;
build(lson, l, mid), build(rson, mid + 1, r);
pushup(u);
}
}
void modify(int u, int L, int R)
{
if(tr[u].l == tr[u].r) tr[u].x = tr[u].sum = sqrt(tr[u].x);
else if(L <= tr[u].l && tr[u].r <= R)
{
if(tr[u].x == 1)
{
return;
}
else
{
int mid = tr[u].l + tr[u].r >> 1;
if(L <= mid) modify(lson, L, R);
if(R > mid) modify(rson, L, R);
pushup(u);
}
}
else
{
int mid = tr[u].l + tr[u].r >> 1;
if(L <= mid) modify(lson, L, R);
if(R > mid) modify(rson, L, R);
pushup(u);
}
}
u64 query(int u, int L, int R)
{
if(L <= tr[u].l && tr[u].r <= R)
{
return tr[u].sum;
}
int mid = tr[u].l + tr[u].r >> 1;
u64 res = 0;
if(L <= mid) res += query(lson, L, R);
if(R > mid) res += query(rson, L, R);
return res;
}
int main()
{
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
scanf("%llu", &a[i]);
}
build(1, 1, n);
int m;
scanf("%d", &m);
for(int i = 1; i <= m; i++)
{
int k, l, r;
scanf("%d%d%d", &k, &l, &r);
if(l > r) swap(l, r);
if(k == 0)
{
modify(1, l, r);
}
else
{
printf("%llu\n", query(1, l, r));
}
}
return 0;
}
用懒标记维护的版本。minlen 表示当前区间涵盖所有区间, c n t cnt cnt 最小的时候区间长度之和.
这样子,维护的其实是覆盖 0 0 0 次的区间长度之和. 最后总长度减去它就可以了.
#include
#define x first
#define y second
#define lowbit(x) ((x) & (-x))
#define all(x) x.begin(), x.end()
#define pcount __builtin_popcount
#define lcount __builtin_clz
#define rcount __builtin_ctz
#define qcin std::ios::sync_with_stdio(false); std::cin.tie(nullptr);
#define tempt template<class T>
#define lson u << 1
#define rson u << 1 | 1
typedef unsigned int u32;
typedef int i32;
typedef unsigned long long u64;
typedef long long i64;
using namespace std;
const int N = 10010;
struct node
{
int l, r, mincnt;
int lazy;
double minlen, len;
}tr[N * 8];
struct seg
{
double x, y1, y2;
int t;
bool operator < (const seg& s) const
{
return x < s.x;
}
}segs[N * 2];
vector<double> ys;
int find(double x)
{
return lower_bound(ys.begin(), ys.end(), x) - ys.begin();
}
void pushup(int u)
{
tr[u].mincnt = min(tr[lson].mincnt, tr[rson].mincnt);
double minlen = 0;
if(tr[u].mincnt == tr[lson].mincnt) minlen += tr[lson].minlen;
if(tr[u].mincnt == tr[rson].mincnt) minlen += tr[rson].minlen;
tr[u].minlen = minlen;
}
void pushdown(int u)
{
tr[lson].mincnt += tr[u].lazy, tr[rson].mincnt += tr[u].lazy;
tr[lson].lazy += tr[u].lazy, tr[rson].lazy += tr[u].lazy;
tr[u].lazy = 0;
}
void build(int u, int l, int r)
{
double len = ys[r + 1] - ys[l];
tr[u] = {l, r, 0, 0, len, len};
if(l == r) return;
else
{
int mid = l + r >> 1;
build(lson, l, mid), build(rson, mid + 1, r);
}
}
void modify(int u, int L, int R, int t)
{
if(L <= tr[u].l && tr[u].r <= R)
{
tr[u].mincnt += t;
tr[u].lazy += t;
}
else
{
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(L <= mid) modify(lson, L, R, t);
if(R > mid) modify(rson, L, R, t);
pushup(u);
}
}
int kase;
int main()
{
int n;
while(cin >> n, n)
{
int sz = 0;
ys.clear();
for(int i = 1; i <= n; i++)
{
double x1, y1, x2, y2;
scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
segs[sz++] = {x1, y1, y2, 1};
segs[sz++] = {x2, y1, y2, -1};
ys.push_back(y1), ys.push_back(y2);
}
sort(ys.begin(), ys.end());
ys.erase(unique(ys.begin(), ys.end()), ys.end());
sort(segs, segs + 2 * n);
build(1, 0, ys.size() - 2);
double ans = 0;
for(int i = 0; i < 2 * n; i++)
{
if(i)
{
double len = tr[1].len;
if(!tr[1].mincnt) len -= tr[1].minlen;
ans += len * (segs[i].x - segs[i - 1].x);
}
modify(1, find(segs[i].y1), find(segs[i].y2) - 1, segs[i].t);
//printf("%f %d %f\n", tr[1].len, tr[1].mincnt, tr[1].minlen);
}
printf("Test case #%d\nTotal explored area: %.2f\n\n", ++kase, ans);
}
return 0;
}
三种操作:
1 a b c
,在建立权值为 c c c 的 a → b a\rightarrow b a→b 的单向边
2 a b c d
,建立 a → [ b , c ] a\rightarrow [b,c] a→[b,c] 权值为 d d d 的单向边
3 a b c d
,建立 [ b , c ] → a [b,c]\rightarrow a [b,c]→a 权值为 d d d 的单向边
给你一个起点,问你起点到其他点的最短路长度
1 ≤ n , q ≤ 1 0 5 1 ≤ n, q ≤ 10^5 1 ≤ n, q ≤ 105
借助线段树建立为每个区间建立一个结点,有一个需要注意的问题,为了保证某个点可以走到它对应的所有区间,并且它所有对应的区间能走到它,并且不能形成长度为 0 0 0 的环。因此可以建立两棵线段树,一棵从父结点向子结点连长度为 0 0 0 的边,一棵从子结点向父结点连长度为 0 0 0 的边. 子结点之间相互连长度为 0 0 0 的边.
当然我们不用真的建立两棵线段树,只需要把对应的线段树编号找到就可以了. 第一个线段树的编号加 4 n 4n 4n 就是第二棵线段树的编号.
#include
#define x first
#define y second
typedef long long ll;
using namespace std;
const int N = 100010, M = 6000010;
const ll INF = 0x3f3f3f3f3f3f3f3fll;
struct node
{
int l, r;
}tr[N * 4];
int h[N * 8], e[M], ne[M], id[N], idx;
ll w[M], d[N * 8];
int n, q, s;
typedef pair<ll, int> P;
bool st[N * 8];
void add(int a, int b, ll c)
{
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
void build(int u, int l, int r)
{
tr[u] = {l, r};
if(l == r)
{
id[l] = u;
add(u, u + 4 * n, 0), add(u + 4 * n, u, 0);
}
else
{
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
add(u, u << 1, 0), add(u, u << 1 | 1, 0);
add((u << 1) + 4 * n, u + 4 * n, 0), add((u << 1 | 1) + 4 * n, u + 4 * n, 0);
}
}
void modify(int u, int l, int r, int x, ll c, int op)
{
if(l <= tr[u].l && tr[u].r <= r)
{
if(op == 2) add(x, u, c), add(x + 4 * n, u + 4 * n, c);
else add(u, x, c), add(u + 4 * n, x + 4 * n, c);
}
else
{
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) modify(u << 1, l, r, x, c, op);
if(r > mid) modify(u << 1 | 1, l, r, x, c, op);
}
}
void dijkstra()
{
memset(d, 0x3f, sizeof d);
d[id[s]] = 0;
priority_queue<P, vector<P>, greater<P>> que;
que.push({0, id[s]});
while(que.size())
{
int u = que.top().y; que.pop();
if(st[u]) continue;
st[u] = true;
for(int i = h[u]; i != -1; i = ne[i])
{
int v = e[i];
if(d[v] > d[u] + w[i])
{
d[v] = d[u] + w[i];
que.push({d[v], v});
}
}
}
}
int main()
{
memset(h, -1, sizeof h);
scanf("%d%d%d", &n, &q, &s);
build(1, 1, n);
for(int i = 1; i <= q; i++)
{
int op, a, b, c, d;
scanf("%d%d%d%d", &op, &a, &b, &c);
if(op == 1)
{
//注意是 id[a] 而不是 a
add(id[a], id[b], c);
add(id[a] + 4 * n, id[b] + 4 * n, c);
}
else if(op == 2)
{
scanf("%d", &d);
//注意是 id[a] 而不是 a
modify(1, b, c, id[a], d, 2);
}
else
{
scanf("%d", &d);
//注意是 id[a] 而不是 a
modify(1, b, c, id[a], d, 3);
}
}
dijkstra();
for(int i = 1; i <= n; i++)
{
//注意是 d[id[i]] 不是 d[i]
printf("%lld%c", d[id[i]] == INF ? -1LL : d[id[i]], i == n ? '\n' : ' ');
}
return 0;
}
题意:给定一棵树。询问 s , t s,t s,t 之间路径上的点权和,点权在 [ a , b ] [a,b] [a,b] 内才有效,否则该点权为 0 0 0
从每个节点向父结点建立可持久化权值线段树,查询的时候就是 r o o t [ s ] + r o o t [ t ] − r o o t [ l c a ( s , t ) ] − r o o t [ f a [ l c a ( s , t ) ] ] root[s] +root[t]-root[lca(s,t)]-root[fa[lca(s,t)]] root[s]+root[t]−root[lca(s,t)]−root[fa[lca(s,t)]].
终于调出来了
第一个错误:主席树由于维护的是权值,查询的是点权之和。因此从子结点向父结点建立版本的时候,要 tr[p].sum += nums[x]
,不是 tr[p].sum = nums[x]
第二个错误:注意找 [ a , b ] [a,b] [a,b] 区间的时候,一个是找 ≥ a \ge a ≥a 的最小值,一个是找 ≤ b \le b ≤b 的最大值,不能全用 lower_bound 处理.
#include
using namespace std;
const int N = 100010, M = N * 2;
typedef long long ll;
int h[N], e[M], ne[M], idx;
ll w[N];
int fa[N][18];
int n, m;
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
int q[N], dep[N];
void bfs()
{
memset(dep, -1, sizeof dep);
dep[0] = 0, dep[1] = 1;
int hh = 0, tt = -1;
q[++tt] = 1;
while(hh <= tt)
{
int u = q[hh++];
for(int i = h[u]; i != -1; i = ne[i])
{
int v = e[i];
if(dep[v] != -1) continue;
dep[v] = dep[u] + 1;
fa[v][0] = u;
for(int j = 1; j < 18; j++) fa[v][j] = fa[fa[v][j - 1]][j - 1];
q[++tt] = v;
}
}
}
int lca(int a, int b)
{
if(dep[a] < dep[b]) swap(a, b);
for(int k = 17; k >= 0; k--)
{
if(dep[fa[a][k]] >= dep[b])
{
a = fa[a][k];
}
}
if(a == b) return a;
for(int k = 17; k >= 0; k--)
{
if(fa[a][k] != fa[b][k])
{
a = fa[a][k], b = fa[b][k];
}
}
return fa[a][0];
}
struct node
{
int l, r;
ll sum;
}tr[N * 4 + N * 17];
int cnt;
vector<ll> nums;
int root[N];
int find(ll x)
{
return lower_bound(nums.begin(), nums.end(), x) - nums.begin();
}
void pushup(int u)
{
tr[u].sum = tr[tr[u].l].sum + tr[tr[u].r].sum;
}
int build(int l, int r)
{
int p = ++cnt;
if(l == r)
{
tr[p] = {l, r, 0};
return p;
}
int mid = l + r >> 1;
tr[p].l = build(l, mid), tr[p].r = build(mid + 1, r);
pushup(p);
return p;
}
int insert(int p, int l, int r, int x)
{
int q = ++cnt;
tr[q] = tr[p];
if(l == r)
{
tr[q].sum += nums[x];
return q;
}
int mid = l + r >> 1;
if(x <= mid) tr[q].l = insert(tr[p].l, l, mid, x);
else tr[q].r = insert(tr[p].r, mid + 1, r, x);
pushup(q);
return q;
}
ll query(int s, int t, int anc, int anc_fa, int l, int r, int L, int R)
{
if(L <= l && r <= R) return tr[s].sum + tr[t].sum - tr[anc].sum - tr[anc_fa].sum;
int mid = l + r >> 1;
ll res = 0;
if(L <= mid) res += query(tr[s].l, tr[t].l, tr[anc].l, tr[anc_fa].l, l, mid, L, R);
if(R > mid) res += query(tr[s].r, tr[t].r, tr[anc].r, tr[anc_fa].r, mid + 1, r, L, R);
return res;
}
void dfs(int u, int fa)
{
root[u] = insert(root[fa], 0, nums.size() - 1, find(w[u]));
for(int i = h[u]; i != -1; i = ne[i])
{
int v = e[i];
if(v == fa) continue;
dfs(v, u);
}
}
int main()
{
while(cin >> n >> m)
{
memset(h, -1, sizeof h);
idx = 0, cnt = 0;
nums.clear();
for(int i = 1; i <= n; i++)
{
scanf("%lld", &w[i]);
nums.push_back(w[i]);
}
sort(nums.begin(), nums.end());
nums.erase(unique(nums.begin(), nums.end()), nums.end());
for(int i = 1; i < n; i++)
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}
bfs();
root[0] = build(0, nums.size() - 1);
dfs(1, 0);
for(int i = 1; i <= m; i++)
{
int s, t;
ll a, b;
scanf("%d%d%lld%lld", &s, &t, &a, &b);
int anc = lca(s, t);
int anc_fa = fa[anc][0];
a = find(a), b = upper_bound(nums.begin(), nums.end(), b) - nums.begin() - 1;
ll ans = query(root[s], root[t], root[anc], root[anc_fa], 0, nums.size() - 1, a, b);
printf("%lld%c", ans, i == m ? '\n' : ' ');
}
}
return 0;
}
题意:给定一颗有 n n n 个叶节点的二叉树。每个叶节点都有一个权值 p i p_i pi(注意,根不是叶节点),所有叶节点的权值构成了一个 1 ∼ n 1 \sim n 1∼n 的排列。对于这棵二叉树的任何一个结点,保证其要么是叶节点,要么左右两个孩子都存在。现在你可以任选一些节点,交换这些节点的左右子树。在最终的树上,按照先序遍历遍历整棵树并依次写下遇到的叶结点的权值构成一个长度为 n n n 的排列,你需要最小化这个排列的逆序对数。输出这个逆序对数。
线段树合并裸题,每个叶节点建立权值线段树,看看交换与不交换对答案的贡献分别是多少
我们要求的是逆序对最小,对于一个节点,逆序对有三种:
我们清楚,交换子树只会对第三种情况产生影响。因此,我们只需要在合并线段树的过程中统计交换子树的逆序对个数 u u u 和不交换子树的逆序对个数 v v v,取 m i n ( u , v ) min(u,v) min(u,v) 累加到答案中就行了。
现在的问题是如何找第三种情况下的逆序对。
很明显,对于除了叶节点的每一个节点:
重点:每一次合并线段树时,递归到除了叶节点的所有节点,都要累加逆序对个数 u,v。
#include
using namespace std;
const int N = 100010;
typedef long long ll;
struct node
{
int l, r, sz;
}tr[N * 50];
int n, idx;
ll res1, res2, ans;
int modify(int l, int r, int x)
{
int q = ++idx;
tr[q].sz = 1;
if(l == r) return q;
int mid = l + r >> 1;
if(x <= mid) tr[q].l = modify(l, mid, x);
else tr[q].r = modify(mid + 1, r, x);
return q;
}
int merge(int p, int q, int l, int r)
{
if(!p || !q) return p ? p : q;
if(l == r)
{
tr[p].sz += tr[q].sz;
return p;
}
else
{
res1 += 1ll * tr[tr[p].r].sz * tr[tr[q].l].sz;
res2 += 1ll * tr[tr[p].l].sz * tr[tr[q].r].sz;
int mid = l + r >> 1;
tr[p].l = merge(tr[p].l, tr[q].l, l, mid);
tr[p].r = merge(tr[p].r, tr[q].r, mid + 1, r);
tr[p].sz = tr[tr[p].l].sz + tr[tr[p].r].sz;
return p;
}
}
int dfs()
{
int x;
scanf("%d", &x);
if(!x)
{
int l = dfs(), r = dfs();
res1 = res2 = 0;
int p = merge(l, r, 1, n);
ans += min(res1, res2);
return p;
}
else
{
int p = modify(1, n, x);
return p;
}
}
int main()
{
scanf("%d", &n);
dfs();
printf("%lld\n", ans);
}
给出 A [ ] A[] A[], 要求支持:
1 询问 A [ l . . r ] A[l..r] A[l..r] 中最⼤的数
2 删除 A [ x ] A[x] A[x], 并且 x + 1 x+1 x+1 以后的元素整体前移
3 在末尾增加⼀个数
用 splay 肯定是可以的。不过线段树更简单
每个位置记录一个 0 / 1 0/1 0/1. 0 0 0 表示数字被删掉, 1 1 1 表示数字没被删。记录当前 [ 1... r ] [1...r] [1...r] 未被删掉数的数量是 s u m [ r ] sum[r] sum[r]. 那么用线段树二分,在 [ 1 , n ] [1,n] [1,n] 上面找第 s u m [ r ] sum[r] sum[r] 小的数字.
对长度为 n n n 的数列进行 m m m 次操作, 操作为:
1 a [ l . . r ] a[l..r] a[l..r] 每⼀项都加⼀个常数 C C C, 其中 0 ≤ C ≤ 1 0 11 0 ≤ C ≤ 10^{11} 0≤C≤1011
2 求 F [ a [ l ] ] + F [ a [ l + 1 ] ] + . . . F [ a [ r ] ] m o d 10000 F[a[l]]+F[a[l+1]]+...F[a[r]] \mod 10000 F[a[l]]+F[a[l+1]]+...F[a[r]]mod10000 的余数
其中 F [ i ] F[i] F[i] 表示斐波那契数列.
即 F [ 0 ] = F [ 1 ] = 1 F[0]=F[1]=1 F[0]=F[1]=1, F [ n + 2 ] = F [ n + 1 ] + F [ n ] F[n+2]=F[n+1]+F[n] F[n+2]=F[n+1]+F[n]
每一项保存 F [ a [ i ] ] F[a[i]] F[a[i]] 与 F [ a [ i + 1 ] ] F[a[i + 1]] F[a[i+1]] 即可,然后用 lazy tag 记下来幂次.
然后操作的时候就是
( F [ a i ] F [ a i + 1 ] ) ( 0 1 1 1 ) \begin{pmatrix}F[a_i] & F[a_i + 1]\end{pmatrix} \begin{pmatrix}0 & 1 \\ 1 & 1\end{pmatrix} (F[ai]F[ai+1])(0111)
每个点维护左起一段连续最长的空住房,以及右起一段连续的最长的空住房,在表示的区间内最长的连续空住房数。
如何合并信息?
前两个很好维护,但是要特判一边全部都是空住房的时候。
最后一个也好维护,首先用两边原来的答案来更新。把两个区间拼接起来会导致中间的连续一段空住房变长,所以还需要用它来更新答案。
对于lazy标记的下放,因为是覆盖,所以直接改就好了。
这道题的WA了很久才过 懒标记之间会有相互限制,就比如这个题,lazy0 和 lazy1 一定不会同时为1,因此一旦修改其中一个是1,另一个必须要修改为0.
当然这道题的懒标记开一个就行了,因为他们是相互限制的嘛.
#include
#include
#include
using namespace std;
const int N = 50010;
struct node
{
int l, r;
int lmax, rmax, mmax;
int lazy;
}tr[N * 4];
int n, m;
void pushup(int u)
{
if(tr[u << 1].mmax == tr[u << 1].r - tr[u << 1].l + 1)
{
tr[u].lmax = tr[u << 1].mmax + tr[u << 1 | 1].lmax;
}
else tr[u].lmax = tr[u << 1].lmax;
if(tr[u << 1 | 1].mmax == tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1)
{
tr[u].rmax = tr[u << 1].rmax + tr[u << 1 | 1].mmax;
}
else tr[u].rmax = tr[u << 1 | 1].rmax;
tr[u].mmax = max(max(tr[u << 1].mmax, tr[u << 1 | 1].mmax), tr[u << 1].rmax + tr[u << 1 | 1].lmax);
}
void pushdown(int u)
{
if(tr[u].lazy != -1)
{
tr[u << 1].lmax = tr[u << 1].rmax = tr[u << 1].mmax = (tr[u << 1].r - tr[u << 1].l + 1) * (tr[u].lazy ^ 1);
tr[u << 1 | 1].lmax = tr[u << 1 | 1].rmax = tr[u << 1 | 1].mmax = (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1) * (tr[u].lazy ^ 1);
tr[u << 1].lazy = tr[u << 1 | 1].lazy = tr[u].lazy;
}
tr[u].lazy = -1;
}
void build(int u, int l, int r)
{
tr[u].l = l, tr[u].r = r;
if(l == r)
{
tr[u].lmax = tr[u].rmax = tr[u].mmax = 1;
tr[u].lazy = -1;
}
else
{
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
pushup(u);
}
}
void modify(int u, int l, int r, int x)
{
if(l <= tr[u].l && tr[u].r <= r)
{
tr[u].lazy = x;
tr[u].lmax = tr[u].rmax = tr[u].mmax = (tr[u].r - tr[u].l + 1) * (x ^ 1);
}
else
{
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) modify(u << 1, l, r, x);
if(r > mid) modify(u << 1 | 1, l, r, x);
pushup(u);
}
}
int query(int u, int k)
{
if(tr[u].mmax < k) return 0;
if(tr[u].l == tr[u].r) return tr[u].l;
pushdown(u);
if(tr[u << 1].mmax >= k) return query(u << 1, k);
if(tr[u << 1].rmax + tr[u << 1 | 1].lmax >= k) return tr[u << 1].r - tr[u << 1].rmax + 1;
else return query(u << 1 | 1, k);
}
int main()
{
scanf("%d%d", &n, &m);
build(1, 1, n);
for(int i = 1; i <= m; i++)
{
int op, l, k;
scanf("%d", &op);
if(op == 1)
{
scanf("%d", &k);
int res = query(1, k);
printf("%d\n", res);
if(res) modify(1, res, res + k - 1, 1);
}
else
{
scanf("%d%d", &l, &k);
modify(1, l, l + k - 1, 0);
}
}
return 0;
}
如题,你需要维护这样的一个长度为 NN 的数组,支持如下几种操作
此外,每进行一次操作(对于操作2,即为生成一个完全一样的版本,不作任何改动),就会生成一个新的版本。版本编号即为当前操作的编号(从1开始编号,版本0表示初始状态数组)
简单的可持久化线段树,不需要建立权值线段树
注意数组千万别开小,因为询问是 1 0 6 10^6 106 次.
#include
using namespace std;
const int N = 1000010;
struct node
{
int l, r, value;
}tr[N * 4 + N * 20];
//小心询问次数达到1e6,数组千万别开小.
int n, m, a[N], root[N], idx;
int build(int l, int r)
{
int q = ++idx;
if(l == r)
{
tr[q].value = a[l];
return q;
}
int mid = l + r >> 1;
tr[q].l = build(l, mid), tr[q].r = build(mid + 1, r);
return q;
}
int modify(int p, int l, int r, int x, int v)
{
int q = ++idx;
tr[q] = tr[p];
if(l == r)
{
tr[q].value = v;
return q;
}
int mid = l + r >> 1;
if(x <= mid) tr[q].l = modify(tr[p].l, l, mid, x, v);
else tr[q].r = modify(tr[p].r, mid + 1, r, x, v);
return q;
}
int query(int p, int l, int r, int x, int& res)
{
int q = ++idx;
tr[q] = tr[p];
if(l == r)
{
res = tr[q].value;
return q;
}
int mid = l + r >> 1;
if(x <= mid) tr[q].l = query(tr[p].l, l, mid, x, res);
else tr[q].r = query(tr[p].r, mid + 1, r, x, res);
return q;
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
root[0] = build(1, n);
for(int i = 1; i <= m; i++)
{
int v, op, loc, value;
scanf("%d%d%d", &v, &op, &loc);
if(op == 1)
{
scanf("%d", &value);
root[i] = modify(root[v], 1, n, loc, value);
}
else
{
int res;
root[i] = query(root[v], 1, n, loc, res);
printf("%d\n", res);
}
}
return 0;
}
给定 n 个集合,第 i 个集合内初始状态下只有一个数,为 i。
有 m 次操作。操作分为 3 种:
1 a b
合并 a,b 所在集合;2 k
回到第 k 次操作(执行三种操作中的任意一种都记为一次操作)之后的状态;3 a b
询问 a,b 是否属于同一集合,如果是则输出 1 ,否则输出 0。按秩合并的并查集,只要 f a fa fa 数组确定, d e p dep dep 状态确定,这个并查集的就唯一确定。
有几个需要注意的地方:
#include
using namespace std;
const int N = 100010, M = 200010;
struct node
{
int l, r, fa, rk;
}tr[N * 4 + 20 * M];
//想清楚内存,是 M * log N.
int n, m, idx, root[M];
int build(int l, int r)
{
int q = ++idx;
if(l == r)
{
tr[q].fa = l;
return q;
}
int mid = l + r >> 1;
tr[q].l = build(l, mid), tr[q].r = build(mid + 1, r);
return q;
}
int modify(int p, int l, int r, int x, int v)
{
int q = ++idx;
tr[q] = tr[p];
if(l == r)
{
tr[q].fa = v;
return q;
}
int mid = l + r >> 1;
if(x <= mid) tr[q].l = modify(tr[p].l, l, mid, x, v);
else tr[q].r = modify(tr[p].r, mid + 1, r, x, v);
return q;
}
void query(int q, int l, int r, int x, int& fa, int& rk)
{
if(l == r)
{
fa = tr[q].fa, rk = tr[q].rk;
return;
}
int mid = l + r >> 1;
if(x <= mid) query(tr[q].l, l, mid, x, fa, rk);
else query(tr[q].r, mid + 1, r, x, fa, rk);
}
int find(int q, int x, int& rk)
{
int fa;
query(q, 1, n, x, fa, rk);
if(fa == x) return x;
return find(q, fa, rk);
}
void add(int q, int l, int r, int x)
{
if(l == r)
{
tr[q].rk++;
return;
}
int mid = l + r >> 1;
if(x <= mid) add(tr[q].l, l, mid, x);
else add(tr[q].r, mid + 1, r, x);
}
int main()
{
scanf("%d%d", &n, &m);
root[0] = build(1, n);
for(int i = 1; i <= m; i++)
{
int op, a, b, k;
scanf("%d", &op);
if(op != 2)
{
scanf("%d%d", &a, &b);
int pa, da, pb, db;
pa = find(root[i - 1], a, da), pb = find(root[i - 1], b, db);
//这个版本信息的更新千万别忘,放在最前面!
root[i] = root[i - 1];
if(op == 1)
{
if(pa == pb) continue;
//高度小的树连在高度大的树下方.
if(da > db) swap(a, b), swap(da, db), swap(pa, pb);
root[i] = modify(root[i - 1], 1, n, pa, pb);
if(da == db) add(root[i], 1, n, pb);
}
else
{
printf("%d\n", int(pa == pb));
}
}
else
{
scanf("%d", &k);
root[i] = root[k];
}
}
}
P3369 【模板】普通平衡树
无旋treap
#include
using namespace std;
const int N = 100010, INF = 0x3f3f3f3f;
mt19937 rnd(0);
struct node
{
int key, l, r, sz, rnk;
}tr[N];
int tot, root;
void pushup(int u)
{
tr[u].sz = tr[tr[u].l].sz + tr[tr[u].r].sz + 1;
}
void split(int rt, int& a, int& b, int k)
{
if(rt == 0)
{
a = b = 0;
return;
}
if(tr[rt].key <= k)
{
a = rt;
split(tr[a].r, tr[a].r, b, k);
}
else
{
b = rt;
split(tr[b].l, a, tr[b].l, k);
}
pushup(rt);
}
void merge(int&rt, int a, int b)
{
if(a == 0 || b == 0)
{
rt = a + b;
return;
}
if(tr[a].rnk < tr[b].rnk)
{
rt = b;
merge(tr[b].l, a, tr[b].l);
}
else
{
rt = a;
merge(tr[a].r, tr[a].r, b);
}
pushup(rt);
}
int add_node(int val)
{
tr[++tot] = {val, 0, 0, 1, rnd()};
return tot;
}
void insert(int& rt, int val)
{
int x = 0, y = 0, nnode = add_node(val);
split(rt, x, y, val);
merge(x, x, nnode);
merge(rt, x, y);
}
void remove(int& rt, int val)
{
int x = 0, y = 0, z = 0;
split(rt, x, y, val);
split(x, x, z, val - 1);
merge(z, tr[z].l, tr[z].r);
merge(x, x, z);
merge(rt, x, y);
}
int get_kth(int rt, int k)
{
while(tr[tr[rt].l].sz + 1 != k)
{
if(tr[tr[rt].l].sz >= k) rt = tr[rt].l;
else
{
k -= tr[tr[rt].l].sz + 1;
rt = tr[rt].r;
}
}
return tr[rt].key;
}
int get_rank(int& rt, int val)
{
int x = 0, y = 0;
split(rt, x, y, val - 1);
int res = tr[x].sz + 1;
merge(rt, x, y);
return res;
}
int get_pre(int&rt, int val)
{
int x = 0, y = 0;
split(rt, x, y, val - 1);
int res = get_kth(x, tr[x].sz);
merge(rt, x, y);
return res;
}
int get_scc(int& rt, int val)
{
int x = 0, y = 0;
split(rt, x, y, val);
int res = get_kth(y, 1);
merge(rt, x, y);
return res;
}
void build()
{
add_node(INF);
root = 1;
tr[root].sz = 0;
}
int main()
{
int n;
scanf("%d", &n);
build();
for(int i = 1; i <= n; i++)
{
int op, x;
scanf("%d%d", &op, &x);
if(op == 1)
{
insert(root, x);
}
else if(op == 2)
{
remove(root, x);
}
else if(op == 3)
{
printf("%d\n", get_rank(root, x));
}
else if(op == 4)
{
printf("%d\n", get_kth(root, x));
}
else if(op == 5)
{
printf("%d\n", get_pre(root, x));
}
else
{
printf("%d\n", get_scc(root, x));
}
}
return 0;
}
P3391 【模板】文艺平衡树
无旋treap
Treap - OI Wiki (oi-wiki.org)
线段树套 无旋treap
给一棵树,有两个操作,修改边权,查询 从 u u u 到 v v v 的路径的边的最大值.
树链剖分裸题,把边权下沉至点权.
#include
#pragma GCC optimize(2)
using namespace std;
const int N = 10010, M = N * 2, INF = 0x3f3f3f3f;
int h[N], e[M], ne[M], we[M], wv[N], idx;
int n;
inline void add(int a, int b, int c)
{
e[idx] = b, ne[idx] = h[a], we[idx] = c, h[a] = idx++;
}
int sz[N], fa[N], dep[N], son[N];
void dfs1(int u, int father, int depth)
{
sz[u] = 1, fa[u] = father, dep[u] = depth;
son[u] = 0;
for(int i = h[u]; i != -1; i = ne[i])
{
int v = e[i];
if(v == father) continue;
wv[v] = we[i];
dfs1(v, u, depth + 1);
sz[u] += sz[v];
if(sz[son[u]] < sz[v]) son[u] = v;
}
}
int id[N], top[N], nw[N], cnt;
void dfs2(int u, int t)
{
id[u] = ++cnt, nw[cnt] = wv[u], top[u] = t;
if(!son[u]) return;
dfs2(son[u], t);
for(int i = h[u]; i != -1; i = ne[i])
{
int v = e[i];
if(v == fa[u] || v == son[u]) continue;
dfs2(v, v);
}
}
struct node
{
int l, r, mmax;
}tr[N * 4];
void pushup(int u)
{
int t1 = tr[u << 1].mmax, t2 = tr[u << 1 | 1].mmax;
tr[u].mmax = t1 > t2 ? t1 : t2;
}
void build(int u, int l, int r)
{
tr[u] = {l, r};
if(l == r)
{
tr[u].mmax = nw[l];
}
else
{
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
pushup(u);
}
}
int query(int u, int l, int r)
{
if(l <= tr[u].l && tr[u].r <= r) return tr[u].mmax;
int mid = tr[u].l + tr[u].r >> 1;
int res = 0;
if(l <= mid)
{
int t = query(u << 1, l, r);
if(t > res) res = t;
}
if(r > mid)
{
int t = query(u << 1 | 1, l, r);
if(t > res) res = t;
}
return res;
}
void modify(int u, int x, int y)
{
if(tr[u].l == x && tr[u].r == x) tr[u].mmax = y;
else
{
int mid = tr[u].l + tr[u].r >> 1;
if(x <= mid) modify(u << 1, x, y);
else if(x > mid) modify(u << 1 | 1, x, y);
pushup(u);
}
}
int query_path(int a, int b)
{
if(a == b) return 0;
int res = 0;
while(top[a] != top[b])
{
if(dep[top[a]] < dep[top[b]]) swap(a, b);
int t = query(1, id[top[a]], id[a]);
if(t > res) res = t;
a = fa[top[a]];
}
if(dep[a] < dep[b]) swap(a, b);
int t = query(1, id[b] + 1, id[a]);
if(t > res) res = t;
return res;
}
int main()
{
int T;
scanf("%d", &T);
while(T--)
{
memset(h, -1, sizeof h);
idx = 0, cnt = 0;
scanf("%d", &n);
for(int i = 1; i < n; i++)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
dfs1(1, -1, 1);
dfs2(1, 1);
build(1, 1, n);
char op[10];
while(scanf("%s", op) && *op != 'D')
{
int a, b;
scanf("%d%d", &a, &b);
if(*op == 'Q') printf("%d\n", query_path(a, b));
else
{
int edge = 2 * (a - 1);
int u = e[edge], v = e[edge ^ 1];
if(u == fa[v]) modify(1, id[v], b);
else modify(1, id[u], b);
}
}
}
return 0;
}
题意:给出一棵节点上有权的树,两种操作:
1.修改一个点的权 w i w_i wi
2.询问 u , v u,v u,v,求 ∑ w i ∗ w j ∑w_i∗w_j ∑wi∗wj, 其中 i到j的路径 和 u到v的路径 有公共点
难,不会
给定一棵有 n n n 个节点的无根树和 m m m 个操作,操作有 2 2 2 类:
1、将节点 a a a 到节点 b b b 路径上所有点都染成颜色 c c c;
2、询问节点 a a a 到节点 b b b 路径上的颜色段数量(连续相同颜色被认为是同一段),如 “ 112221 112221 112221” 由 3 3 3 段组成:“ 11 11 11”、“ 222 222 222” 和 “ 1 1 1”。
请你写一个程序依次完成这 m m m 个操作。
链剖裸题,套个线段树。
这题唯一难的就在于查询颜色段的时候要考虑跨越查询区间时,要记得去掉重复的部分。合并区间的时候看一下两边区间相接的部分是否是相同颜色,相同的话减掉就可以了。
线段树维护这一段区间的左端点颜色,右端点颜色,区间不同颜色的和,记得 l a z y lazy lazy 标记。
一个只有点的图
三种操作:link
操作, update
操作,query
链上的点权和
动态树(LCT
)
有 N N N 个车站 [ 1 , n ) [1,n) [1,n) 的车站可以买到编号为 i + 1 i+1 i+1 到 a i a_i ai 的车站的票,设 p i j p_{ij} pij 为 i i i 车站到 j j j 车站的最少买票数,求 ∑ i = 1 n ∑ j = i + 1 n p i j \sum\limits_{i=1}^{n}\sum\limits_{j=i + 1}^{n}p_{ij} i=1∑nj=i+1∑npij
n ≤ 1 0 5 n \le 10^5 n≤105
设 d p i dp_i dpi 为从第 i i i 个车站出发,到达 [ i + 1 , n ] [i+1,n] [i+1,n] 车站需要的最小车票数量之和. 我们考虑 d p i dp_i dpi 从哪个 d p m dp_m dpm 转移过来,答案是在 m ∈ [ i + 1 , a i ] m \in [i + 1,a_i] m∈[i+1,ai] 找到 a m a_m am 最小的 m m m. 可以这么考虑:从 i i i 开始走两步可以到达的点,经过 m m m 可以到达最远的点,其他点能到的 m m m 也可以到. 因此拿 m m m 当中转节点是最优的.
上下有两个长度为n、位置对应的序列A、B,两个序列都是全排列
其中数的范围均为 1 ∼ n 1\sim n 1∼n。若 a b s ( A [ i ] − B [ j ] ) < = 4 abs(A[i]-B[j]) <= 4 abs(A[i]−B[j])<=4,
则 A [ i ] A[i] A[i] 与 B [ j ] B[j] B[j] 间可以连一条边。现要求在边与边不相交的情况下的最大的连边数量。
找到 A [ i ] A[i] A[i] 对应的 B B B 数组中的 9 9 9 个数字,然后把这 9 9 9 个数字按照降序排序,推进同一个数组里面,最后在此数组求最长上升子序列就是答案.
P1439 【模板】最长公共子序列
给出 1 , 2 , … , n 1,2,\ldots,n 1,2,…,n 的两个排列 P 1 P_1 P1 和 P 2 P_2 P2 ,求它们的最长公共子序列。
对于 P 1 P_1 P1 的每一个数 i i i,记录 s e q [ i ] seq[i] seq[i] 为 i i i 在 P 2 P_2 P2 中的位置. 那么答案就是 s e q seq seq 的最长上升子序列。因为 P 1 P_1 P1 的下标上升的同时,要保证 P 2 P_2 P2 的下标也是上升的的.
#include
#define x first
#define y second
using namespace std;
typedef long long ll;
const int N = 200010;
ll ans;
int a[N], n, tr[N];
int lowbit(int x)
{
return x & -x;
}
int sum(int x)
{
int res = 0;
for(int i = x; i; i -= lowbit(i)) res += tr[i];
return res;
}
void add(int x, int c)
{
for(int i = x; i <= n; i += lowbit(i)) tr[i] += c;
}
typedef pair<int, int> P;
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
priority_queue<P, vector<P>, greater<P>> que;
for(int i = 1; i <= n; i++)
{
while(que.size() && que.top().x < i)
{
add(que.top().y, -1);
que.pop();
}
//printf("%d %d %d\n", i, a[i], sum(a[i]));
ans += sum(min(a[i], n));
que.push({a[i], i});
add(i, 1);
}
printf("%lld\n", ans);
return 0;
}
题意:给定一个 n n n 个结点的树,第 i i i 号点的权值是 2 i 2^i 2i,现在删除 k k k 个点,并且保证剩下的节点都是连通的,而且要保证权值之和最大。问需要删除哪些结点?
可以考虑贪心,由于我们只要能选择较大的结点就一定要选,因为此节点的权值大于比他编号小的所有节点权值之和。因为 n n n 号点是必须要选择的,因此以 n n n 号点为根,把 n n n 加入树 T ′ T' T′ 中。现在开始从大到小把结点加入树中,然后找到从 i i i 到 T ′ T' T′ 的最短路径。
法一:采用树上倍增的方法快速解决(记录一下已经在 T ′ T' T′ 中的结点,然后倍增法计算出来它到最近的在 T ′ T' T′ 中的祖先的距离)。
法二:采用树状数组。怎样优化一个新节点 (记为 u u u ) 到离 T ′ T' T′ 中点 (记为 v v v) 的距离的最小值?即 u u u 到根节点的距离减去 u u u 到根节点的路径中已标明的结点的数量。已标明的结点数量怎么算呢?建立一个树的深搜序,相当于单点修改树的结点权值,把0改为1,然后查询某一结点到根节点的结点权值之和. 那么修改某个结点 u u u 权值,影响路径权值之和的结点是以 u u u 为子树的结点。那么在 [ i n [ u ] , o u t [ u ] ] [in[u], out[u]] [in[u],out[u]] 之间加1即可.
法二代码(万分小心树状数组那俩函数千万别写错!)
#include
using namespace std;
const int N = 1000010, M = N * 2;
int h[N], e[M], ne[M], idx;
int n, m, tr[N];
vector<int> ans;
bool st[N];
inline void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
int lowbit(int x)
{
return x & -x;
}
//别把两个函数搞反
void modify(int x, int c)
{
//别写成 i += lowbit(x)
for(int i = x; i <= n; i += lowbit(i))
{
//别写成 tr[i] += x
tr[i] += c;
}
}
int sum(int x)
{
int res = 0;
for(int i = x; i; i -= lowbit(i))
{
res += tr[i];
}
return res;
}
int cnt, in[N], out[N], fa[N], dep[N];
void dfs(int u, int father, int depth)
{
in[u] = ++cnt, fa[u] = father, dep[u] = depth;
for(int i = h[u]; i != -1; i = ne[i])
{
int v = e[i];
if(v == father) continue;
dfs(v, u, depth + 1);
}
out[u] = cnt;
}
int main()
{
memset(h, -1, sizeof h);
scanf("%d%d", &n, &m);
m = n - m;
for(int i = 1; i < n; i++)
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}
dfs(n, 0, 1);
for(int i = n; i; i--)
{
if(dep[i] - sum(in[i]) <= m)
{
//printf("** %d %d\n", dep[i], sum(in[i]));
for(int u = i; u && !st[u]; u = fa[u])
{
st[u] = true;
modify(in[u], 1), modify(out[u] + 1, -1);
m--;
//printf("%d %d %d\n", i, u, m);
}
}
}
for(int i = 1; i <= n; i++) if(!st[i]) ans.push_back(i);
sort(ans.begin(), ans.end());
for(int i = 0, sz = ans.size(); i < sz; i++)
{
printf("%d%c", ans[i], i + 1 == sz ? '\n' : ' ');
}
return 0;
}