CF--#670

C. Link Cut Centroids

**题意:**给定一棵树,若该树有两个重心,就删一条边,再连一条边,使得树的重心为一个。

**思路:**先求出一个重心,再根据重心的性质:若有两个重心,两重心相邻,且所有节点到重心的距离之和最小,若第二个重心存在,就求出,再将其中一个重心的儿子练到另外一个重心上,就可以了。其实我也不知道为什么,大胆猜想!!!

当时dfs里的continue写成了return,害得我wa了三发。。。。还以为猜错了呢

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define eps 1e-9
#define INF 0x3f3f3f3f
#define pb push_back
#define mk make_pair
#define fi first
#define se second
using namespace std;
typedef unsigned int UI;
typedef unsigned long long ull;
typedef long long ll;
typedef pair<int, int> P;
const int N = 1e5 + 10;

struct edge{
    int u, v;
    int next;
}es[N * 2];
int head[N], cnt;
void add_edge(int u, int v) {
    es[cnt].u = u;
    es[cnt].v = v;
    es[cnt].next = head[u];
    head[u] = cnt++;
}
int n;

int vis[N];//重心标记
int siz[N], mson[N];//siz是子树的顶点大小,mson是最大子树的大小
//int dis[MAX_N]; //这个是距离数组,算路径时需要
int root, sum, tot, ans;//root用来更新重心
                        //sum是当前查询重心的子树的大小
                        //tot是表示距离数组的上界
void get_root(int u, int fa) { //v为当前节点(当前的根),fa为v的父亲, sn是查找重心的树的大小
    siz[u] = 1; mson[u] = 0;  //siz是以u为顶点的子树大小,mson是以u为分割点的最大子树
    for (int i = head[u]; i != -1; i = es[i].next) {
        int v = es[i].v;
        if (v == fa || vis[v]) continue;//回到父亲或者下一节点是重心标记过了,直接跳过
        get_root(v, u);  //递归
        siz[u] += siz[v]; //以u为根的子树大小
        mson[u] = max(mson[u], siz[v]); //求最大子树
    }
    mson[u] = max(mson[u], sum - siz[u]); //当前最大子树和若以u为顶点的子树顶点-当前最大子树, 相比较
    if (mson[u] < mson[root]) root = u;   //最大子树的顶点数最小的子树
}

//在main函数中,第一次查重心时
//先将root置为0,将mson[0] = INF, 方便查找时更新

int dis;
void dfs(int u, int fa, int dep) {
    dis += dep;
    for (int i = head[u]; i != -1; i = es[i].next) {
        int v = es[i].v;
        if (v == fa) continue;
        dfs(v, u, dep + 1);
    }
}

int main () {
    int t;
    scanf("%d", &t);
    while (t--) {
        scanf("%d", &n);
        cnt = 0;
        for (int i = 0; i <= n; i++) {
            head[i] = -1;
            vis[i] = 0;
            siz[i] = mson[i] = 0;
        }
        for (int i = 1, u, v; i < n; i++) {
            scanf("%d%d", &u, &v);
            add_edge(u, v);
            add_edge(v, u);
        }
        sum = n;
        root = 0, mson[0] = INF;
        get_root(1, 0);
//        printf("重心:%d\n", root);

        dis = 0;
        dfs(root, 0, 0);
        int dd = dis;
        bool f = true;
        int root2;
        for (int i = head[root]; i != -1; i = es[i].next) {
            dis = 0;
            int v = es[i].v;
            dfs(v, 0, 0);
            if (dis == dd) {
                root2 = v;
                f = false;
                break;
            }
        }
        if (f) {
            int v = es[head[root]].v;
            printf("%d %d\n", root, v);
            printf("%d %d\n", root, v);
        }
        else {
            int v;
            for (int i = head[root]; i != -1; i = es[i].next) {
                int p = es[i].v;
                if (p != root2) v = p;
            }
            printf("%d %d\n", root, v);
            printf("%d %d\n", root2, v);
        }
    }
    return 0;
}

D. Three Sequence

题意: 给定一个数组 a a a , 自己构造两个数组 b b b c c c,使得 a i = b i + c i a_i = b_i + c_i ai=bi+ci ,并且 b b b 为非严格递增, c c c 为非严格递减。 q q q 次询问,每次在区间 [ l , r ] + x [l,r] +x [l,r]+x 后求 m i n ( m a x ( b i , c j ) ) min(max(b_i, c_j)) min(max(bi,cj))

思路: 因为 b n ≥ b i b_n \geq b_i bnbi c 1 ≥ c i c_1 \geq c_i c1ci ,所以 m i n ( m a x ( b i , c j ) ) = m i n ( m a x ( b n , c 1 ) ) min(max(b_i,c_j)) = min(max(b_n,c_1)) min(max(bi,cj))=min(max(bn,c1))

​ 构造思路: i ≥ 2 i \geq 2 i2 时,若 t p = a i − a i − 1 ≥ 0 tp= a_i - a_{i-1} \geq 0 tp=aiai10 ,就在 b i = b i − 1 + t p , c i = c i − 1 b_i = b_{i-1} + tp, c_i = c_{i-1} bi=bi1+tp,ci=ci1

​ 若 t p < 0 tp < 0 tp<0 ,就 c i = c i − 1 + t p , b i = b i − 1 c_i = c_{i-1} + tp, b_i = b_{i-1} ci=ci1+tp,bi=bi1

​ 这样 b n = ∑ i = 2 n ( a i − a i − 1 ) > 0  大于0的累加 b_n = \sum_{i=2}^{n}(a_i - a_{i-1})>0 \text{ 大于0的累加} bn=i=2n(aiai1)>0 大于0的累加 ,所以 答案就是 m a x ( c 1 , a 1 − c 1 + b n ) max(c_1, a_1 - c1 + b_n) max(c1,a1c1+bn)

​ 即求的是 a 1 + ∑ i = 2 n ( a i − a i − 1 ) > 0  大于0的累加 a_1 + \sum_{i=2}^{n}(a_i - a_{i-1})>0 \text{ 大于0的累加} a1+i=2n(aiai1)>0 大于0的累加,就是 a a a 的差分数组的第一项和之后大于0 的总和。每次询问只要 O ( 1 ) O(1) O(1) 维护差分数组,得到总和就能得到答案。

代码:

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;

int n;
int main() {
    scanf("%d", &n);
    vector<ll> a(n + 2);
    for (int i = 1; i <= n; i++)
        scanf("%lld", &a[i]);
    vector<ll> f(n + 2);
    f[1] = a[1];
    for (int i = 2; i <= n; i++) {
        f[i] = a[i] - a[i - 1];
    }
    ll sum = a[1];
    for (int i = 2; i <= n; i++) {
        if (f[i] > 0) sum += f[i];
    }
    ll ans;
    if (sum >= 0) {
        ans = sum / 2 + sum % 2;
    }
    else {
        ans = sum / 2;
    }
    printf("%lld\n", ans);
    int q;
    scanf("%d", &q);
    while (q--) {
        int l, r;
        ll x;
        scanf("%d%d%lld", &l, &r, &x);
        ll tl = f[l];
        ll tr = f[r + 1];
        if (x != 0) {
            f[l] += x;
            f[r + 1] -= x;

            if (x > 0) {
                if (l == 1) sum += x;
                if (tl <= 0 && f[l] > 0 && l != 1) sum += f[l];
                else if (tl > 0 && l != 1) sum += x;

                if (r + 1 <= n) {
                    if (tr > 0) sum -= min(x, tr);
                }
            }
            else {
                if (l == 1) sum += x;
                if (tl > 0 && l != 1) sum -= min(tl, -x);

                if (r + 1 <= n) {
                    if (tr <= 0 && f[r + 1] > 0) sum += f[r + 1];
                    else if (tr > 0) sum -= x;
                }
            }
        }
        if (sum >= 0) {
            ans = sum / 2 + sum % 2;
        }
        else {
            ans = sum / 2;
        }
        printf("%lld\n", ans);
    }
    return 0;
}


​			

​		
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值