关于我想了很久才想出这题咋做这档事 - 5

#0.0 目录


目录中以下题目皆可点击查看原题

#1.0 P1967 [NOIP2013 提高组] 货车运输

#1.1 变图为树

初次看到该题是本人在练习树剖的时候,当时拿过题面来二话不说就写了个树剖,然后发现 每个点之间道路不唯一 ....(当然树剖应该也可以做,不过不是上来不处理就剖)

不难想到,在走从 \(x\)\(y\) 的某一条路径时,限制货车装载量的是该条路径上的最小限重,那我们就应该让所选取的路径上的最小限重尽可能大,那怎么选?最大生成树.可以保证选出的边限重尽可能大。

最大生成树的构建与最小生成树大同小异,我们只需将 Kruskal 算法中的排序变为从大到小排即可。

注意到 初始图不一定联通,那么我们会求出一个最大生成树森林。

#1.2 变树为链

现在,题目变成了:求从 \(x\)\(y\) 的路径上的最小边权。

一看到树上的路径类问题,容易想到:树链剖分,但是,树剖太麻烦了,还不好调,我还懒得写,那怎么办?别急,我们慢慢来。

\(x\)\(y\) 的路径就是从 \(x\)\(\text{LCA}(x,y)\),再从 \(\text{LCA(x,y)}\)\(y\),那我们就可以求从 \(x\)\(\text{LCA}(x,y)\) 的最小边权,再求从 \(\text{LCA(x,y)}\)\(y\) 的最小边权,比较两者的大小即可,那怎么求 \(\text{LCA}(x,y)\)

答:树链剖分How old are you?(怎么老是你?)显然倍增啊!

所以,我们可以设 \(f_{i,k}\) 表示 \(i\)\(2^k\) 辈祖先,\(g_{i,k}\) 表示从 \(i\)\(i\)\(2^k\) 辈祖先的路径上的最小边权,易知:

\[f_{i,k} = f_{f_{i,k-1},k-1},g_{i,k}=\min(g_{i,k-1},g_{f_{i,k-1},k-1}). \]

那么 \(f_{i,0}\) 便是 \(i\) 的父节点,\(g_{i,0}\) 便是从 \(i\) 到其父节点的边的权值。

这样的话,我们就可以用倍增预处理 \(f_{i,k}\)\(g_{i,k}\),这里要注意我们之前求得的是最大生成树森林,可能有多于一颗的最大生成树。所以我们不能单单只从一个点开始。

#1.3 回答

至于回答时,可以直接采用倍增求 \(\text{LCA}\) 的框架,只不过在每次向上跳后更新下答案即可

#1.4 代码

const int N = 10010;
const int M = 50010;
const int INF = 0x3fffffff;

struct Edge{
    int u,v;
    int w,nxt;
};
Edge e[M << 2],es[M];

int tot,n,m,head[N],fa[N],vis[N],rt[N];
int f[N][50],t,d[N],g[N][50],qe;

queue <int> q;

inline int cmp(const Edge a,const Edge b){
    return a.w > b.w;
}

inline void add(int u,int v,int w){
    e[++ tot].u = u;
    e[tot].v = v;
    e[tot].w = w;
    e[tot].nxt = head[u];
    head[u] = tot;
}

inline int getf(int x){
    while (x != fa[x])
      x = fa[x] = fa[fa[x]];
    return x;
}

inline void maxest(){ //求最大生成树
    for (int i = 1;i <= n;i ++)
      fa[i] = i;
    sort(es + 1,es + m + 1,cmp);
    for (int i = 1;i <= m;i ++){
        int fx = getf(es[i].u);
        int fy = getf(es[i].v);
        if (fx == fy) continue;
        add(es[i].u,es[i].v,es[i].w); //将最大生成树按邻接表存图
        add(es[i].v,es[i].u,es[i].w);
        fa[fx] = fy;
    }
}

inline void prework(){ //倍增的预处理算法
    tot = 0;mset(g,0x3f); //memset(g,0x3f,sizeof(g));
    for (int i = 1;i <= n;i ++){ //这是一个最大生成树森林,所以不能只从一个点开始
        if (vis[i]) continue;
        rt[++ tot] = i;vis[i] = true;
        q.push(i);
        while (q.size()){
            int now = q.front();q.pop();
            for (int j = head[now];j;j = e[j].nxt){
                int y = e[j].v;
                if (vis[y]) continue;
                f[y][0] = now;d[y] = d[now] + 1;
                g[y][0] = e[j].w;vis[y] = true;
                for (int k = 1;k <= t;k ++){
                    f[y][k] = f[f[y][k - 1]][k - 1];
                    g[y][k] = min(g[y][k - 1],g[f[y][k - 1]][k - 1]);
                }
                q.push(y);
            }
        }
    }
}

inline int ExLCA(int x,int y){ //以倍增求 LCA 为框架
    int res = INF;
    if (d[x] < d[y]) swap(x,y);
    for (int i = t;i >= 0;i --) //跳到同一高度
      if (d[f[x][i]] >= d[y]){
          res = min(res,g[x][i]);
          x = f[x][i];
      }
    if (x == y) return res;
    for (int i = t;i >= 0;i --) //同时向上跳
      if (f[x][i] != f[y][i]){
          res = min(res,g[y][i]);
          y = f[y][i];
          res = min(res,g[x][i]);
          x = f[x][i];
      }
    res = min(res,g[x][0]); //更新答案
    res = min(res,g[y][0]);
    return res;
}

int main(){
    scanf("%d%d",&n,&m);
    t = (int)(log(n) / log(2)) + 1;
    for (int i = 1;i <= m;i ++)
      scanf("%d%d%d",&es[i].u,&es[i].v,&es[i].w);
    maxest();prework();
    scanf("%d",&qe);
    while (qe --){
        int x,y;
        scanf("%d%d",&x,&y);
        if (getf(x) != getf(y)){puts("-1");continue;} //判断是否联通
        printf("%d\n",ExLCA(x,y));
    }
    return 0;
}

#2.0 P4116 Qtree3

#2.1 简单分析

熟悉的树上操作,又要见到我们的老朋友——树链剖分

线段树中存储深度最浅的黑色节点的编号,正常的树剖即可。

#2.2 代码

const int N = 100010;
const int INF = 0x3fffffff;

struct Edge{
    int u,v;
    int nxt;
};
Edge e[N << 1];

struct Node{
    int l,r;
    int ls,rs;
    int col;
    int min_d,min_p; 
    //min_d 为胜读最浅的黑色节点的深度,其实可以不存,它相当于 d[min_p]
};
Node p[N << 2];

int n,q,tot,head[N],cnt;
int d[N],f[N],size[N],son[N];
int top[N],rk[N],id[N];

inline void add(int u,int v){
    e[++ tot].u = u;
    e[tot].v = v;
    e[tot].nxt = head[u];
    head[u] = tot;
}

inline void dfs1(int x,int fa,int depth){ //正常树剖即可
    d[x] = depth;f[x] = fa;size[x] = 1;
    for (int i = head[x];i;i = e[i].nxt){
        if (e[i].v == fa) continue;
        dfs1(e[i].v,x,depth + 1);
        size[x] += size[e[i].v];
        if (size[e[i].v] > size[son[x]])
          son[x] = e[i].v;
    }
}

inline void dfs2(int x,int t){
    top[x] = t;id[x] = ++ tot;rk[tot] = x;
    if (!son[x]) return;
    dfs2(son[x],t);
    for (int i = head[x];i;i = e[i].nxt){
        if (e[i].v == f[x] || e[i].v == son[x])
          continue;
        dfs2(e[i].v,e[i].v);
    }
}

inline void pushup(int k){ //更新节点信息
    if (p[p[k].ls].min_d < p[p[k].rs].min_d){
        p[k].min_d = p[p[k].ls].min_d;
        p[k].min_p = p[p[k].ls].min_p;
    }
    else {
        p[k].min_d = p[p[k].rs].min_d;
        p[k].min_p = p[p[k].rs].min_p;
    }
}

inline void build(int k,int l,int r){
    if (l == r){
        p[k].l = p[k].r = l;
        p[k].min_d = INF;
        p[k].min_p = 0;
        p[k].col = 0;
        return;
    }
    int mid = (l + r) >> 1;
    p[k].ls = ++ cnt;
    build(p[k].ls,l,mid);
    p[k].rs = ++ cnt;
    build(p[k].rs,mid + 1,r);
    p[k].l = p[p[k].ls].l;
    p[k].r = p[p[k].rs].r;
    p[k].min_d = INF; //别忘了初始化QwQ
    p[k].min_p = 0;
}

inline void change(int k,int x){ //单点修改
    if (p[k].l == p[k].r){
        p[k].col ^= 1;
        if (p[k].col){ //要更改叶节点信息
            p[k].min_d = d[rk[p[k].l]];
            p[k].min_p = rk[p[k].l];
        }
        else {
            p[k].min_p = 0;
            p[k].min_d = INF;
        }
        return;
    }
    int mid = (p[k].l + p[k].r) >> 1;
    if (x <= mid)
      change(p[k].ls,x);
    else
      change(p[k].rs,x);
    pushup(k);
}

inline int query(int k,int x,int y){
    if (x <= p[k].l && p[k].r <= y)
      return p[k].min_p;
    int res = 0,mid = (p[k].l + p[k].r) >> 1;
    if (x <= mid)
      res = query(p[k].ls,x,y);
    if (y > mid){
        int gt = query(p[k].rs,x,y);
        if (d[res] > d[gt])
          res = gt;
    }
    return res;
}

inline int ask(int x){ //树剖求 LCA 的框架
    int res = 0;
    while (top[x] != 1){ //从 1 到 x 的路径
        int gt = query(1,id[top[x]],id[x]);
        if (d[res] > d[gt])
          res = gt;
        x = f[top[x]];
    }
    int gt = query(1,id[1],id[x]);
    if (d[res] > d[gt])
      res = gt;
    return res;
}

int main(){
    cin >> n >> q;
    for (int i = 1;i < n;i ++){
        int u,v;
        cin >> u >> v;
        add(u,v),add(v,u);
    }
    
    dfs1(1,0,1);
    tot = 0;
    dfs2(1,1);
    build(++ cnt,1,n);
    
    d[0] = INF;
    while (q --){
    	int opt,b;
        cin >> opt >> b;
    	if (!opt)
    	  change(1,id[b]);
    	else{
    	    int res = ask(b);
    	    if (res) cout << res << endl;
    	    else cout << -1 << endl;
        }
    }
	return 0;
}

#3.0 P2801 教主的魔法

#3.1 简单分析

容易看出,这道题用线段树不好做,那就暴力分块.

分块后,对于每一个 a[i],都有一个替身使者 d[i] 与之对应,对于 每一个块内d[i] 进行从小到大的排序,注意不是整体排序

修改的话,对于不是整块的点,要对 a[i] 进行更改,再将新的 a[i] 赋值给 d[i],再重新对块内的 d[i] 排序。对于整块的点,显然块中的相对大小没有改变,直接用 lazy[] 数组记录修改即可。

查询时,对于不是整块的点,朴素的扫一遍即可;但对于整块的,要在 d[i] 中二分查找第一个大于等于 \(x\) 的位置,用该块右端点编号减去查找到的位置编号再加一便是块中大于等于 \(x\) 的数的数量(这个活好像 lower_bound() 就能干)

#3.2 代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#define ll long long
#define mset(l,x) memset(l,x,sizeof(l))
using namespace std;

const int N = 1000010;
const int INF = 0x3fffffff;

int n,q,id[N],sub[N][2],len;
ll a[N],d[N],lazy[N];

inline int cmp(const ll &a,const ll &b){
    return a < b;
}

inline void change(int l,int r,ll w){ //修改
    int lp = id[l],rp = id[r];
    if (lp == rp){
        for (int i = l;i <= r;i ++)
          a[i] += w;
        for (int i = sub[lp][0];i <= sub[lp][1];i ++)
          d[i] = a[i];
        sort(d + sub[lp][0],d + sub[lp][1] + 1,cmp);
        return;
    }
    for (int i = l;i <= sub[lp][1];i ++) //朴素修改
      a[i] += w;
    for (int i = sub[lp][0];i <= sub[lp][1];i ++)
      d[i] = a[i];
    sort(d + sub[lp][0],d + sub[lp][1] + 1,cmp);
    for (int i = sub[rp][0];i <= r;i ++)
      a[i] += w;
    for (int i = sub[rp][0];i <= sub[rp][1];i ++)
      d[i] = a[i];
    sort(d + sub[rp][0],d + sub[rp][1] + 1,cmp);
    for (int i = lp + 1;i < rp;i ++)
      lazy[i] += w;
}

inline ll query(int l,int r,int x){ //查询
    int lp = id[l],rp = id[r];
    ll res = 0;
    if (lp == rp){
        for (int i = l;i <= r;i ++)
          if (a[i] + lazy[lp] >= x) res ++;
        return res;
    }
    for (int i = l;i <= sub[lp][1];i ++) //朴素统计
      if (a[i] + lazy[lp] >= x) res ++;
    for (int i = sub[rp][0];i <= r;i ++)
      if (a[i] + lazy[rp] >= x) res ++;
    for (int i = lp + 1;i < rp;i ++){
        int ls = sub[i][0],rs = sub[i][1],ans = rs + 1;
        while (ls <= rs){ //二分查找
            int mid = (ls + rs) >> 1;
            if (d[mid] + lazy[i] >= x)
              ans = mid,rs = mid - 1;
            else ls = mid + 1;
        }
        res += (sub[i][1] - ans + 1);
    }
    return res;
}

int main(){
    scanf("%d%d",&n,&q);
    len = sqrt(n);
    for (int i = 1;i <= n;i ++){
        scanf("%lld",&a[i]);
        d[i] = a[i];
        id[i] = (i - 1) / len + 1;
    }
    for (int i = 1;i < id[n];i ++){
        sub[i][0] = (i - 1) * len + 1;
        sub[i][1] = i * len;
        sort(d + sub[i][0],d + sub[i][1] + 1,cmp);
    }
    sub[id[n]][0] = (id[n] - 1) * len + 1;
    sub[id[n]][1] = n;
    sort(d + sub[id[n]][0],d + sub[id[n]][1] + 1,cmp);
    
    while (q --){
        char opt;
        int l,r,w;
        cin >> opt >> l >> r >> w;
        if (opt == 'M')
          change(l,r,w);
        else 
          printf("%lld\n",query(l,r,w)); 
    }
    return 0;
}
posted @ 2021-04-18 15:10  Dfkuaid  阅读(43)  评论(0编辑  收藏  举报