八月十九日个人训练(线段树模板)

目录


前言

两部分,一部分是acwing线段树问题模板,另一部分是18日杭电多校补题

一、线段树模板

1.AcWing 1275. 最大数

#include<iostream>
using namespace std;

const int N = 2e5 + 5;
typedef long long LL;

//线段树的结点, 最大空间开4倍
struct Node{
    int l, r;
    int v; 
}tr[N * 4];

int m, p;

//u为当前线段树的结点编号
void build(int u, int l, int r) {
    tr[u] = {l, r};
    if(l == r) return;
    int mid = l + r >> 1;
    build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
}

//查询以u为根节点,区间[l, r]中的最大值
int query(int u, int l, int r) {
    if(tr[u].l >= l && tr[u].r <= r) return tr[u].v;

    int mid = tr[u].l + tr[u].r >> 1;
    int v = 0;
    if(l <= mid) v = query(u << 1, l, r);

    if(r > mid) v = max(v, query(u << 1 | 1, l, r));

    return v;
}

//u为结点编号,更新该结点的区间最大值
void modify(int u, int x, int v) {
    if(tr[u].l == tr[u].r) tr[u].v = v;  //叶节点,递归出口
    else {
        int mid = tr[u].l + tr[u].r >> 1;
        if(x <= mid) modify(u << 1, x, v);
        else modify(u << 1 | 1, x, v);

        tr[u].v = max(tr[u << 1].v, tr[u << 1 | 1].v);
    }
}

int main()
{
    int n = 0, last = 0;  
    cin >> m >> p;

    build(1, 1, m);  

    while(m--) 
    {
        char op;
        cin >> op;
        if(op == 'A')      
        {
            int t;
            cin >> t;
            //在n + 1处插入
            modify(1, n + 1, ((LL)t + last) % p);
            n++;
        }
        else
        {
            int L;
            cin >> L;
            //查询[n - L + 1, n]内的最大值,u = 1,即从根节点开始查询
            last = query(1, n - L + 1, n);
            cout << last << endl;
        }
    }

    return 0;
}

2.AcWing 245. 你能回答这些问题吗

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 500010;
int n, m, w[N];
struct Node{
    int l, r, lmax, rmax, tmax, sum;
} tr[4 * N];

void pushup(Node &u, Node &l, Node &r)                  //由子区间更新父区间
{
    u.sum = l.sum + r.sum;                              //区间总和
    u.lmax = max(l.lmax, l.sum + r.lmax);               //前缀和 max(左子区间前缀和,左子区间总和+右子区间前缀和)
    u.rmax = max(r.rmax, r.sum + l.rmax);               //后缀和 max(右子区间后缀和,右子区间总和+左子区间后缀和)
    u.tmax = max(max(l.tmax, r.tmax), l.rmax + r.lmax); //区间最大值 max(左子区间前缀和,右子区间后缀和,(左子区间后缀和+右子区间前缀和))
}

void pushup(int u)
{
    pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}

void build(int u, int l, int r)                         //建树
{
    tr[u].l = l, tr[u].r = r;                           
    if ( l == r )
        tr[u].lmax = w[l], tr[u].rmax = w[l], tr[u].sum = w[l], tr[u].tmax = w[l];
    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 x, int v)                        //修改
{
    if ( tr[u].l == x && tr[u].r == x ) 
        tr[u].lmax = v, tr[u].rmax = v, tr[u].tmax = v, tr[u].sum = v;
    else
    {
        int mid = tr[u].r + tr[u].l >> 1;
        if ( mid >= x ) modify(u << 1, x, v);           //往左递归(画图好理解)
        else modify(u << 1 | 1, x, v);                  //往右递归
        pushup(u);                                      //修改后更新信息
    }
}

Node query(int u, int l, int r)                         //查询,由于存在跨左右子区间的情况,返回结构体类型,再用pushup计算结果
{
    if ( tr[u].l >= l && tr[u].r <= r ) return tr[u];   //如果当前区间在查询区间内
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if ( mid >= r ) return query(u << 1, l, r);     
        else if ( mid < l ) return query(u << 1 | 1, l, r);
        else                                            //有跨子区间的情况,处理左右子区间,再用pushup求结果
        {
            Node left = query(u << 1, l ,r);
            Node right = query(u << 1 | 1, l, r);
            Node res;                                   //res看作left和right的父区间
            pushup(res, left, right);
            return res;
        }
    }
}

int main()
{
    cin >> n >> m;
    for ( int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
    build(1, 1, n);
    while ( m -- )
    {
        int k, x, y;
        scanf("%d%d%d", &k, &x, &y);
        if ( k == 1 ) 
        {
            if ( x > y ) swap(x, y);
            printf("%d\n", query(1, x, y).tmax);
        }
        else modify(1, x, y);
    }
    return 0;
}

3.AcWing 246. 区间最大公约数

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;

typedef long long LL;
const int N = 500010;
int n, m;           //n个数,m个询问
LL w[N];
struct Node{
    int l, r;
    LL sum, d;
}tr[4 * N];

LL gcd(LL a, LL b)  //求最大公约数
{
    return b ? gcd(b, a % b) : a;
}

void pushup(Node &u, Node &l, Node &r)  //由子区间信息更新父区间信息
{
    u.sum = l.sum + r.sum;
    u.d = gcd(l.d, r.d);
}

void pushup(int u)
{
    pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}

void build(int u, int l, int r)         //建树
{
    tr[u].l = l, tr[u].r = r;
    if ( l == r ) 
        tr[u].d = w[l] - w[l - 1], tr[u].sum = w[l] - w[l - 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 x, LL v)         //修改
{
    if ( tr[u]. r == x && tr[u].l == x ) 
        tr[u].d = tr[u].sum + v, tr[u].sum += v;    
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if ( mid >= x ) modify(u << 1, x, v);
        else modify(u << 1 | 1, x, v);
        pushup(u);
    }
}

Node query(int u, int l, int r)
{
    if ( tr[u].l >= l && tr[u].r <= r ) return tr[u];
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if ( mid >= r ) return query(u << 1, l, r);
        else if ( mid < l ) return query(u << 1 | 1, l, r);
        else
        {
            Node left = query(u << 1, l, r);        //如果当前访问区间的子区间横跨询问区间
            Node right = query(u << 1 | 1, l, r);   //则递归两个子区间
            Node res;                               //res相当于left和right的父区间
            pushup(res, left, right);               //相当于求right和left区间合并后的结果
            return res;
        }
    }
}

int main()
{
    cin >> n >> m;
    for ( int i = 1; i <= n; i ++ ) scanf("%lld", &w[i]);
    build(1, 1, n);
    int l, r;
    LL d;
    char op[2];
    while ( m -- )
    {
        scanf("%s%d%d", op, &l, &r);
        if ( * op == 'C' ) 
        {
            scanf("%lld", &d);
            modify(1, l, d);                            //差分操作l处加d,r+1处减d
            if ( r + 1 <= n ) modify(1, r + 1, -d);     //注意判断r+1与n的关系
        }
        else
        {
            Node left = query(1, 1, l);                 //gcd(a[l])
            Node right = {0, 0, 0, 0};                  //若l+1>r
            if ( l + 1 <= r ) right = query(1, l + 1, r);   //gcd(b[l+1]~b[r])
            printf("%lld\n", abs(gcd(left.sum, right.d)));  //输出正数
        }
    }
    return 0;
}

4.AcWing 243. 一个简单的整数问题2

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 100010;

int n, m;
int w[N];
struct Node
{
    int l, r;
    LL sum, add;
}tr[N * 4];

void pushup(int u)
{
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}

void pushdown(int u)
{
    auto &root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
    if (root.add)
    {
        left.add += root.add, left.sum += (LL)(left.r - left.l + 1) * root.add;
        right.add += root.add, right.sum += (LL)(right.r - right.l + 1) * root.add;
        root.add = 0;
    }
}

void build(int u, int l, int r)
{
    if (l == r) tr[u] = {l, r, w[r], 0};
    else
    {
        tr[u] = {l, r};
        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 d)
{
    if (tr[u].l >= l && tr[u].r <= r)
    {
        tr[u].sum += (LL)(tr[u].r - tr[u].l + 1) * d;
        tr[u].add += d;
    }
    else    // 一定要分裂
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) modify(u << 1, l, r, d);
        if (r > mid) modify(u << 1 | 1, l, r, d);
        pushup(u);
    }
}

LL query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum;

    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    LL sum = 0;
    if (l <= mid) sum = query(u << 1, l, r);
    if (r > mid) sum += query(u << 1 | 1, l, r);
    return sum;
}


int main()
{
    scanf("%d%d", &n, &m);

    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);

    build(1, 1, n);

    char op[2];
    int l, r, d;

    while (m -- )
    {
        scanf("%s%d%d", op, &l, &r);
        if (*op == 'C')
        {
            scanf("%d", &d);
            modify(1, l, r, d);
        }
        else printf("%lld\n", query(1, l, r));
    }

    return 0;
}

上面是部分线段树模板

二、杭电多校补题

1.Wavy Tree

只有两种情况(低高低高低高)(高低高低高低),所以从第一位开始找,每次比后面一个数大或者小,否则需要改动后面一个数,改动数值总和即为该种情况下操作最小值,然后两者取最小

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
vector<int>v;
const int N = 1e6+10;
int n, m ;
int a[N],b[N];
ll sx,sy;
void slove(){
	scanf("%d",&n);
	sx = sy = 0;
	for(int i = 1; i <= n; i ++){
		scanf("%d",&a[i]);
		b[i] = a[i];
	}
	int cnt = 0;
	for(int i = 1; i < n; i ++){
		cnt = a[i+1];
		if(i & 1){
			if(a[i] >= cnt){
				sx += a[i] - cnt + 1;
				a[i+1] = a[i] + 1;
			}
		}else{
			
			if(a[i] <= cnt){
				sx += cnt - a[i] + 1;
				a[i+1] = a[i] - 1;
			}

		}

	}
	for(int i = 1; i < n; i ++){
		cnt = b[i+1];
		if(i & 1){
			if(b[i] <= cnt){
				sy += cnt - b[i] + 1;
				b[i+1] = b[i] - 1;
			}
		}else{
			
			if(b[i] >= cnt){
				sy += b[i] - cnt + 1;
				b[i+1] = b[i] + 1;
			}

		}

	}
	// cout <<"sx="<<sx<<" "<<"sy="<<sy<<endl;
	cout << min(sx,sy)<<endl;
}

int main(){
	
	int tt;
	cin >> tt;
	while(tt--){
		slove();

	}

	return 0;
}

2.Average Replacement

参考题解

结论题,最后我们记录每个联通块的 ∑a[i]∗(deg[i]+1) 和 ∑deg[i]+1 ,最后除一下就好了。

 

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
vector<int>v;
const int N = 2e5+10;
int n, m, a[N] ;
double ans[N];
int h[N],e[N*2],ne[N*2],idx;
void add(int a,int b){
	e[++idx] = b;
	ne[idx] = h[a];
	h[a] = idx;
}

int st[N],sz[N],d[N];
ll sum[N];

void bfs(int x){
	queue<int>q;
	st[x] = x;
	q.push(x);
	while(q.size()){
		int t = q.front();
		q.pop();
		sz[x] += d[t] + 1;
		sum[x] += 1ll * (d[t] + 1) * a[t];
		for(int i = h[t]; i; i = ne[i]){
			int j = e[i];
			if(st[j]) continue;
			st[j] = x;
			q.push(j);
		}
	}
}

void slove(){
	scanf("%d%d",&n,&m);
	idx = 0;
	for(int i = 1; i <= n; i ++){
		scanf("%d",&a[i]);
		st[i] = sz[i] = sum[i] = h[i] = d[i] = 0;
	}
	while(m--){
		int x,y;
		scanf("%d%d",&x,&y);
		d[x]++,d[y]++;
		add(x,y),add(y,x);
	}

	for(int i = 1; i <= n; i ++) if(!st[i]) bfs(i);

	for(int i = 1; i <= n; i ++){
		if(sz[i] != 0) ans[i] = 1.0 * sum[i] / sz[i];
	}

	for(int i = 1; i <= n; i ++) printf("%.6lf\n",ans[st[i]]);
	

}

int main(){
	
	int tt;
	scanf("%d",&tt);
	while(tt--){
		slove();

	}

	return 0;
}

3.Even Tree Split

树型dp,从每个点都跑一遍,处理出所有树的长度,如果他的子树是偶数,上面也是偶数,则这条边可以分割,统计出所有边即可

#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef long long ll;
const int N = 1e5+10,mod = 998244353;
int n, m ;
int h[N],e[N*2],ne[N*2],idx;
int su[N];
int res;
void add(int a,int b){
	e[++idx] = b;
	ne[idx] = h[a];
	h[a] = idx;
}

void dfs(int u,int f){
	su[u] = 1;
	for(int i = h[u]; i; i = ne[i]){
		int j = e[i];
		if(j == f) continue;
		dfs(j,u);
		su[u] += su[j];
	}
}

void dfs1(int u,int f){
	for(int i = h[u]; i; i = ne[i]){
		int j = e[i];
		if(j == f) continue;
		if(su[j] % 2 == 0) res++;
		dfs1(j,u);
	}
}

ll show(int a,int k){
	ll ans = 1;
	while(k){
		if(k&1) ans = ans * a % mod;
		a = a * a % mod;
		k /= 2;
	}
	return ans;
}

void slove(){
	cin >> n;
	idx = 0;
	for(int i = 0; i <= n; i ++) h[i] = 0;
	for(int i = 1; i < n; i ++){
		int x,y;
		cin >> x >> y;
		add(x,y),add(y,x);
	}
	res = 0;
	dfs(1,0);
	dfs1(1,0);
	// cout << "res="<<res << endl;

	cout << show(2,res) - 1 << endl;	

}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	int tt;
	cin >> tt;
	while(tt--){
		slove();
	}

	return 0;
}

4.Painting Game

Bob先手,他想要棋子尽可能多,所有必定从第一个开始,A想尽可能少,会间隔2格,Bob再落子需要间隔三格,这样就需要在三格中间落子,剩余空白地方更少,棋子更多。

A先手的话,会从第二个地方开始,然后会间隔5个空格落子,这样才能确保空白地方更多,棋子更少,模拟可以发现规律

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
vector<int>v;
const int N = 2;
int n, m ;
string s;
int show(int x){
	int sx = x / 7;
	x %= 7;
	if(x == 0) return sx * 3;
	if(x == 1 || x == 2) return sx * 3 + 1;
	if(x == 3 || x == 4) return sx * 3 + 2;
	if(x == 5 || x == 6) return sx * 3 + 3;
}
void slove(){
	cin >> n;
	cin >> s;
	if(s[0] == 'A'){
		if(n <= 3) cout << 1 << endl;
		else cout << show(n-3)+1<<endl;
	}else{
		if(n <= 2) cout << 1 << endl;
		else cout << show(n) << endl;
	}
}

int main(){
	
	int tt;
	cin >> tt;
	while(tt--){
		slove();

	}

	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值