#include <iostream>
#include <algorithm>
#include <cmath>
#include <string>
#include <vector>
#include <map>
using namespace std;
#define maxn 100007//元素总个数
#define ls l,m,rt*2
#define rs m+1,r,rt*2+1
int Sum[maxn * 2], Add[maxn << 2];//Sum求和,Add懒标记
int A[maxn], n;//存原数组数据下标
//PushUp函数更新节点信息,此处是求和
void PushUp(int rt) { Sum[rt] = Sum[rt * 2] + Sum[rt * 2 + 1]; }
//建树 Build函数
void Build(int L, int r, int rt) {
//l,r表示当前节点区间,rt表示当前节点编号
//以节点rt为根节点,建立[l,r]区间的线段树
if (L == r) {
Sum[rt] = A[L];//存储数组的值
return;
}
int mid = L + (r - L) / 2;//二分建树
Build(L, mid, rt * 2);//左树
Build(mid + 1, r, rt * 2 + 1);//右树
PushUp(rt);//节点rt值存储
return;
}
//A[k]+=c;
void Update(int k, int c, int L, int r, int rt) {
//在[L,r]区间的以rt为根节点的线段树上,数组元素a[k]加c
if (L == r) {
Sum[rt] += c;
return;
}
int mid = L + (r - L) / 2;
if (k <= mid)Update(k, c, L, mid, rt * 2);
else
Update(k, c, mid + 1, r, rt * 2 + 1);
PushUp(rt);//更新根结点
}
//下推标记函数
void PushDown(int rt, int ln, int rn) {
//ln,rn是左子树、右子树的数量
//将节点rt的懒标记加到其左右孩子上,并撤销rt上的该标记
if (Add[rt]) {
//下推标记
Add[rt * 2] += Add[rt];
Add[rt * 2 + 1] += Add[rt];
//修改子节点的Sum使其与对应的Add对应
Sum[rt * 2] += Add[rt] * ln;
Sum[rt * 2 + 1] += Add[rt] * rn;
//清本节点rt的标记
Add[rt] = 0;
}
}
//区间修改A[LL,rr]+=c
void Update(int LL, int rr, int c, int L, int r, int rt) {
//在区间[L,r]中更新区间[LL,rr]的值 - -全部加c
//LL,L,r,rr
if (LL <= L && rr <= r) {
Sum[rt] += c * (r - L + 1);//更新数字和
Add[rt] += c;//加懒标记,表示本区间的Sum正确,但是子区间需要根据Add的值调整
return;
}
int mid = L + (r - L) / 2;
PushDown(rt, mid - L + 1, r - mid);//下推标记
//待操作区间[LL,rr]处理在rt的左右子树上
//LL,L,rr,r---L,LL,r,rr,
if (LL <= mid)Update(LL, rr, c, L, mid, rt * 2);
if (rr > mid)Update(LL, rr, c, mid + 1, rt * 2 + 1);
PushUp(rt);//更新本节点信息
}
int Query(int LL, int rr, int L, int r, int rt) {
//LL,rr表示操作区间,L,r表示当前节点区间,rt表示当前节点编号
//查找节点rt
if (LL <= L && rr <= r) {
//在区间内,直接返回
return Sum[rt];
}
int mid = L + (r - L) / 2;
//下推标记,否则Sum可能不正确
PushDown(rt, mid - L + 1, r - mid);
//累计答案
int ans = 0;
if (LL <= mid)ans += Query(LL, rr, L, mid, rt * 2);
if (rr > mid)ans += Query(LL, rr, mid + 1, r, rt * 2 + 1);
}
int main() {
int n = 100;
Build(1, n, 1);//建树
int L = 10, C = 9;
Update(L, C, 1, n, 1);//点修改
int R = 20; L = 15;
Update(L, R, C, 1, n, 1);//区间修改
//区间查询
int ans = Query(L, R, 1, n, 1);
return 0;
}
代码关于下标,原始数据下标,节点下标,节点表示的区间[l,r]
原始数组下标从0/1开始,节点表示区间的意义,节点下标与原始数组下标的关系
求最大值 (nowcoder.com)https://ac.nowcoder.com/acm/problem/14402
给出一个序列,你的任务是求每次操作之后序列中 (a[j]-a[i])/(j-i)【1<=i<j<=n】的最大值。
操作次数有Q次,每次操作需要将位子p处的数字变成y.
输入描述:
本题包含多组输入,每组输入第一行一个数字n,表示序列的长度。 然后接下来一行输入n个数,表示原先序列的样子。 再接下来一行一个数字Q,表示需要进行的操作的次数。 最后Q行每行两个元素p,y,表示本操作需要将位子p处的数字变成y. 数据范围: 3<=n<=200000 1<=Q<=200000 -1000000000<=a[i]<=1000000000
输出描述:
每组数据输出Q行,每行输出一个浮点数,保留两位小数,表示所求的最大值。
示例1
输入
5 2 4 6 8 10 2 2 5 4 9
5 2 4 6 8 10 2 2 5 4 9
输出
3.00 3.00
3.00 3.00
说明
第一次操作之后的序列变成:2 5 6 8 10 第二次操作之后的序列变成:2 5 6 9 10
备注:
输入只有整形
分析:a[j]-a[i])/(j-i)【1<=i<j<=n】的最大值,单独一个数组元素不是这个所求,不符合线段树定义
不符合区间加法
转化1:求最大值,而最大值一定是相邻两个元素的差求出来的
直观可以看出来,最大值就是相邻两个数求得的;几
何上看,求得像是两点之间的斜率,可知相邻两点之间的最大
怎么由输入数据得到线段树的节点数据,注意改一个数组元素,可能有两个节点信息改变线段树单点修改
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn = 200000;
int a[maxn + 10];
int b[maxn + 10];
struct SegMent {
int l, r;
int val;
}que[maxn * 4];
void PushUp(int s) {
que[s].val = max(que[s << 1].val, que[s << 1 | 1].val);
return;
}
void buildT(int L, int R, int s = 1) {
que[s].l = L;
que[s].r = R;
if (R == L) {
que[s].val = a[L];
return;
}
int mid = (L + R) / 2;
buildT(L, mid, s << 1);
buildT(mid + 1, R, s << 1 | 1);
que[s].val = max(que[s << 1].val, que[s << 1 | 1].val);
//PushUp(s);
return;
}
void update(int k, int y, int s = 1) {
if (que[s].l == que[s].r) {
if (que[s].l == k)
que[s].val = y;
return;
}
int mid = (que[s].l + que[s].r) / 2;
if (k <= mid)update(k, y, s << 1);
else
update(k, y, s << 1 | 1);
que[s].val = max(que[s << 1].val, que[s << 1 | 1].val);
//PushUp(s);
}
int query(int s = 1) {
if (que[s].l == que[s].r) {
return que[s].val;
}
return max(query(s * 2), query(s * 2 + 1));
}
int main() {
int n, q;
while (scanf("%d", &n) != EOF) {
scanf("%d", &b[1]);
for (int i = 2; i <= n; i++)
{
scanf("%d", &b[i]);
a[i - 1] = b[i] - b[i - 1];
}
scanf("%d", &q);
buildT(1, n - 1, 1);
for (int i = 1; i <= q; i++) {
int p, y;
scanf("%d%d", &p, &y);
b[p] = y;
if (p <= n - 1) {
a[p] = b[p + 1] - y;
update(p, a[p], 1);
}
if (p > 1) {
a[p - 1] = y - b[p - 1];
update(p - 1, a[p - 1], 1);
}
printf("%.2lf\n", 1.0 * que[1].val);
}
}
return 0;
}
随机树
平日里写hash的时候,总有某些选手由于脸黑而导致惨遭卡模数,然后一些恶意卡模数的出题人也因此身败名裂。为了防止被卡,我们用一种高级的随机方式来代替原来的线性随机生成,也就是所谓的随机树!
现在有一棵编号为0~n-1的有根树,其中0是树的根。每个节点初始有一个值Ti。现在要求支持一下两种操作:
1. 给出两个正整数u和x,我们将Tu的值乘以x,我们将这种操作称为SEED操作。
2. 给出一个正整数i,询问Si以及它一共有多少个正约数。其中Si表示以i为根的子树所有点的权值的乘积,我们将这种操作称为RAND操作。
容易发现,这样得到的答案还是很随机的。(其实不是)
你需要回答每一次的询问,由于一个数的约数个数可能非常多,这个数也可以非常大,你只需要把答案对1e9+7取模就可以了。
输入描述:
第一行一个正整数n,表示节点个数。 接下来n-1行,每行两个正整数u和v,表示u是v的父节点。 接下来一行n个正整数,分别表示每个节点的初始权值Ti。 接下来一行一个正整数q,表示操作的个数。 接下来q行,每行是以下两种情况之一: 1. SEED u x 表示将u节点的权值乘以x。 2. RAND i 表示询问Si以及它一共有多少个正约数。
输出描述:
每一行两个整数,对应一个RAND操作,你需要输出所求的权值以及它的正约数个数,答案对于1e9+7取模即可。
示例1
输入
8 0 1 0 2 1 3 2 4 2 5 3 6 3 7 7 3 10 8 12 14 40 15 3 RAND 1 SEED 1 13 RAND 1
8 0 1 0 2 1 3 2 4 2 5 3 6 3 7 7 3 10 8 12 14 40 15 3 RAND 1 SEED 1 13 RAND 1
输出
14400 63 187200 126
14400 63 187200 126
备注:
对于20%的数据,1 ≤ n, q ≤ 10。
对于40%的数据,1 ≤ n, q ≤ 100。
对于60%的数据,1 ≤ n, q ≤ 2000。
对于80%的数据,1 ≤ n, q ≤ 50000。
对于100%的数据,1 ≤ n, q ≤ 100000。
另外请注意,所有读入的数一定满足1 ≤ x ≤ 109 。
同时,数据保证在任意时刻,每个点的权值不可能拥有超过13的素因子,也就是说,每个数的素因子最多只有2,3,5,7,11,13这六种可能。
线段树是处理连续区间的区间和问题
转化,求DFS序列,得出连续区间,再利用线段树求解
参考别人程序改的
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <map>
typedef long long ll;
using namespace std;
#define ls L,mid,s<<1
#define rs mid+1,R,s<<1|1
const int maxn = 1e5 + 5;
const ll mod = 1e9 + 7;
const ll prime[6] = { 2,3,5,7,11,13 };
struct SegNode {
int L, R;
int sum[6];
}que[maxn << 2];
int n, q, cnt;
int in[maxn], out[maxn], value[maxn], factor[6];
vector<int >connect[maxn];
map<int, int> id;
//cnt是连续区间,从1开始
void Dfs(int u) {//从节点u开始深搜
in[u] = ++cnt;//入口标记
id[cnt] = u;//??id,根据顺序cnt查出节点u--转化为连续的区间信息
int size = connect[u].size();//节点u的下一个节点--子节点
for (int i = 0; i < size; i++)
Dfs(connect[u][i]);
out[u] = cnt;//出口标记
}
ll qpow(int a, int b) {//计算a^b对mod的余
//ll ans = 0;//????
ll ans = 1;
while (b > 0) {
if (b & 1)ans = ans * a % mod;
a = (ll)a * a % mod;
b >>= 1;
}
return ans;
}
void build(int L, int R, int s) {
que[s].L = L;
que[s].R = R;
int mid = (que[s].L + que[s].R) / 2;
memset(que[s].sum, 0, 6 * sizeof(int));
if (L == R) {//赋值
for (int i = 0; i < 6; i++) {
while (value[id[L]] % prime[i] == 0) {
que[s].sum[i]++;
value[id[L]] /= prime[i];
}
}
return;
}
build(L, mid, s << 1);
build(mid + 1, R, s << 1 | 1);
for (int i = 0; i < 6; i++)
que[s].sum[i] = que[s << 1].sum[i] + que[s << 1 | 1].sum[i];
}
void update(int L, int R, int num,int s) {
if (L <= que[s].L && R >= que[s].R) {
for (int i = 0; i < 6; i++) {
while (num % prime[i] == 0) {
que[s].sum[i]++;
num /= prime[i];
}
}
return;
}
int mid = (que[s].L + que[s].R) / 2;
if (L <= mid)update(L, R, num, s << 1);
if (R > mid)update(L, R, num, s << 1 | 1);
for (int i = 0; i < 6; i++)
que[s].sum[i] = que[s << 1].sum[i] + que[s << 1 | 1].sum[i];
return;
}
void query(int L, int R, int s) {
if (L <= que[s].L && R >= que[s].R) {
for (int i = 0; i < 6; i++)
factor[i] += que[s].sum[i];
return;
}
int mid = (que[s].L + que[s].R) / 2;
if (L <= mid)query(L, R, s << 1);
if (R > mid)query(L, R, s << 1 | 1);
return;
}
int main() {
scanf("%d", &n);
//节点关联的下标从1开始
for (int i = 1; i <= n - 1; i++)
{
int u, v;
scanf("%d %d", &u, &v);
connect[u].push_back(v);
}
cnt = 0;
Dfs(0);
for (int i = 0; i <= n - 1; i++)scanf("%d", &value[i]);
build(1, n, 1);//线段树,cnt区间是[1,n] - - 数组下标从1到n,线段树根节点下标是1--id[cnt]对应的值域有0,1,2,3,4,5,6,7---value[i]中的i - in[id]-->cnt
char mes[5];
int x;
ll t;
scanf("%d", &q);
for (int k = 1; k <= q; k++) {
scanf("%s %d", mes, &x);
if (mes[0] == 'S') {
scanf("%lld", &t);
update(in[x], in[x], t, 1);//点修改
}
if (mes[0] == 'R') {
memset(factor, 0, 6 * sizeof(int));
query(in[x], out[x], 1);
ll s = 1, ans = 1;
for (int i = 0; i < 6; i++) {
s = s * qpow(prime[i], factor[i]) % mod;
ans = ans * (factor[i] + 1) % mod;//????排列组合
}
printf("%lld %lld", s, ans);
}
}
return 0;
}
复写一版
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9 +7;
const int maxn = 1e5 +10;
int n,q,cnt;
vector<vector<int>> connect(maxn);
//vector<int> connect[maxn];
int in[maxn],out[maxn],value[maxn];
int prime[6] = {2,3,5,7,11,13};
int queryRes[15];
map<int,int>id;
struct SegNode{
int L,R;
int sum[6];
}que[maxn<<2];
void Dfs(int u){
in[u] = ++cnt;
id[cnt] = u;
int size = connect[u].size();
for(int i = 0;i < size;i++)
Dfs(connect[u][i]);
out[u] = cnt;
}
//a^b mod
ll qpow(int a,int n){
ll ans = 1;
while(n>0){
if(n&1){
ans = a*ans%mod;
}
a = (ll)a*a%mod;
n>>=1;
}
return ans;
}
void build(int L,int R,int s){
que[s].L = L;
que[s].R = R;
memset(que[s].sum,0,6*sizeof(int));
if(L == R){
for(int i = 0;i < 6;i++){
while(value[id[L]]%prime[i] == 0)
{
que[s].sum[i]++;
value[id[L]]/=prime[i];
}
}
return;
}
int mid = (L+R)/2;
build(L,mid,s<<1);
build(mid+1,R,s<<1|1);
for(int i = 0;i < 6;i++)
que[s].sum[i] = que[s<<1].sum[i]+que[s<<1|1].sum[i];
return;
}
void update(int k,int c,int s){
if(que[s].L == que[s].R){
for(int i = 0;i < 6;i++){
while(c%prime[i] == 0)
{
que[s].sum[i]++;
c /= prime[i];
}
}
return;
}
int mid = (que[s].L+que[s].R)/2;
if(k <= mid)update(k,c,s<<1);
else update(k,c,s<<1|1);
for(int i = 0;i < 6;i++)
que[s].sum[i] = que[s<<1].sum[i]+que[s<<1|1].sum[i];
return;
}
void query(int L,int R,int s){
if(que[s].L>=L&&que[s].R<=R){
for(int i = 0;i < 6;i++)
queryRes[i]+=que[s].sum[i];
return;
}
int mid = (que[s].L+que[s].R)/2;
if(mid>=L)query(L,R,s<<1);
if(mid<R)query(L,R,s<<1|1);
}
int main(){
scanf("%d",&n);
for(int i = 1;i <= n-1;i++){//控制输入次数
int u,v;
scanf("%d %d",&u,&v);
connect[u].push_back(v);
}
int cnt = 0;
Dfs(0);
for(int i = 0;i < n;i++)//i也是节点的标号
scanf("%d",&value[i]);
build(1,n,1);
scanf("%d",&q);
for(int k = 0;k < q;k++){
char op[6];
int u;
scanf("%s %d",op,&u);
if(op[0] == 'S'){
int x;
scanf("%d",&x);
update(in[u],x,1);
}
if(op[0] == 'R'){
memset(queryRes,0,sizeof(queryRes));
query(in[u],out[u],1);
ll s = 1,ans = 1;
for(int i = 0;i < 6;i++){
s =s*qpow(prime[i],queryRes[i])%mod;
ans = ans*(queryRes[i]+1)%mod;
}
printf("%lld %lld\n",s,ans);
}
}
return 0;
}
注意怎么转化,DFS的具体细节
cnt初始值是1,进行Dfs(0),Dfs对应下面的
void Dfs(int u){
in[u] = cnt;
id[cnt] = u;
cnt++;
int size = connect[u].size();
for(int i = 0;i < size;i++)
Dfs(connect[u][i]);
out[u] = cnt;
}
这是错误的
DFS序时间戳
void Dfs(int u){
in[u] = ++cnt;
id[cnt] = u;
int size = connect[u].size();
for(int i = 0;i < size;i++)
Dfs(connect[u][i]);
out[u] = cnt;
}
cnt初始值是0;
DFS序,是按照深搜时间顺序的节点编号序列,数组下角标存的是时间。
时间戳,是按照深搜时间顺序的时间编号序列,数组下角标存的是节点编号。
id[i]=x,i是时间,x是节点,id是DFS序。
dfn[x]=i,i是时间,x是节点,dfn是时间戳。
对于树状数组,推荐下面链接树状数组彻底入门 - 半根毛线 - 博客园 (cnblogs.com)https://www.cnblogs.com/hsd-/p/6139376.html