[dsu on tree] BZOJ5040. 未来研究

原题好像是莫队加点技巧O(nn),我用莫队加配对堆搞过去了…

这题因为区间不交叉,根据包含关系可以建出一棵树,然后就相当于求子树的信息,可以用启发式合并,但是这样可能会多一个log,那么就用dsu on tree了

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

using namespace std;

typedef long long ll;

const int N=1000010;

int n,q,a[N];

inline char nc(){
  static char buf[100000],*p1=buf,*p2=buf;
  return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}

inline void read(int &x){
  char c=nc(); x=0;
  for(;c>'9'||c<'0';c=nc());for(;c>='0'&&c<='9';x=x*10+c-'0',c=nc());
}

struct Qry{
  int l,r,g;
  friend bool operator <(Qry a,Qry b){
    return a.l<b.l || (a.l==b.l && a.r>b.r);
  }
}Q[N];

int cnt,G[N],S[N],vis[N],b[N],tot,t;
ll ans[N],sum[N],ti[N],mx,tms;

struct edge{
  int t,nx;
}E[N];

inline void addedge(int x,int y){
  E[++cnt].t=y; E[cnt].nx=G[x]; G[x]=cnt;
}

void PutAns(ll x){
  if(x>=10) PutAns(x/10); putchar(x%10+'0');
}

int size[N],son[N];

void pfs(int x){
  size[x]=1;
  for(int i=G[x];i;i=E[i].nx){
    pfs(E[i].t);
    if(son[x]==-1 || size[E[i].t]>size[son[x]]) son[x]=E[i].t;
    size[x]+=size[E[i].t];
  }
}

void add(int x){
  if(Q[x].l==Q[x].r){
    int cur=a[Q[x].l];
    sum[cur]++;
    mx=max(mx,sum[cur]*b[cur]);
    return ;
  }
  for(int i=G[x];i;i=E[i].nx) add(E[i].t);
}

void del(int x){
  if(Q[x].l==Q[x].r) sum[a[Q[x].l]]--;
  for(int i=G[x];i;i=E[i].nx) del(E[i].t);
}

void dfs(int x){
  for(int i=G[x];i;i=E[i].nx)
    if(E[i].t!=son[x])
      dfs(E[i].t),del(E[i].t),mx=0;
  if(~son[x]) dfs(son[x]);
  if(Q[x].l==Q[x].r){
    int cur=a[Q[x].l]; sum[cur]++;
    mx=max(mx,sum[cur]*b[cur]);
  }
  for(int i=G[x];i;i=E[i].nx)
    if(E[i].t!=son[x]) add(E[i].t);
  if(~Q[x].g) ans[Q[x].g]=mx;
}

int main(){
  freopen("1.in","r",stdin);
  freopen("1.out","w",stdout);
  read(n); read(q); tot=q;
  for(int i=1;i<=n;i++)
    read(a[i]),b[i]=a[i];
  sort(b+1,b+n+1); t=unique(b+1,b+1+n)-b-1;
  for(int i=1;i<=n;i++)
    a[i]=lower_bound(b+1,b+1+t,a[i])-b;
  t=0;
  for(int i=1;i<=q;i++){
    read(Q[i].l),read(Q[i].r),Q[i].g=i;
    if(Q[i].l==Q[i].r) vis[Q[i].l]=1;
  }
  for(int i=1;i<=n;i++)
    if(!vis[i]) Q[++tot].l=i,Q[tot].r=i,Q[tot].g=-1;
  sort(Q+1,Q+1+tot); Q[0].r=n+1;
  son[0]=-1;
  for(int i=1;i<=tot;i++){
    while(Q[S[t]].r<Q[i].l) t--;
    addedge(S[t],i);
    S[++t]=i; son[i]=-1;
  }
  pfs(0); dfs(0);
  for(int i=1;i<=q;i++)
    PutAns(ans[i]),putchar('\n');
  return 0;
}
### 并查集 (DSU) 的基本概念 并查集(Disjoint Set Union, DSU),也称为联合-查找数据结构,是一种树形的数据结构,用于处理一些不相交集合的合并及查询问题。这种数据结构支持两种操作:`find` 和 `merge`。 ### Find 方法的作用及实现 `find` 函数的主要目的是找到某个节点所属集合的代表元。为了优化性能,在执行查找的同时会进行路径压缩,使得该集合中的每一个成员都直接连接到根节点上。这不仅提高了后续查找的速度,而且简化了内部结构[^1]。 ```cpp int find(int x) { if (parent[x] != x) { // 如果当前结点不是自己的父节点,则递归寻找其祖先 parent[x] = find(parent[x]); // 路径压缩:将x直接连向它的祖宗节点 } return parent[x]; // 返回最终找到的那个祖先作为整个集合的代表者 } ``` ### Merge 方法的作用及实现 `merge` 或称作 `union` 操作负责将两个不同的集合合并成一个新的集合。具体做法是通过调用两次 `find` 来获取各自集合的代表元素,然后让其中一个成为另一个的父亲即可完成合并过程。通常还会加入按秩合并策略以进一步提升效率。 ```cpp void merge(int x, int y) { int rootX = find(x); int rootY = find(y); if (rootX == rootY) return; // 已经属于同一个集合则无需再做任何事情 // 将较小的一棵树接到较大的一棵下面,保持平衡性 if (rank[rootX] < rank[rootY]) swap(rootX, rootY); parent[rootY] = rootX; if (rank[rootX] == rank[rootY]) ++rank[rootX]; } ``` ### 嵌套 Find 的情况分析 当存在多层嵌套调用 `find` 函数时,由于每次都会触发路径压缩机制,因此即使最初形成了一条很长链表式的子树,在经历一次完整的遍历之后也会变得非常扁平化。这意味着对于任意给定的输入序列而言,实际运行时间接近于常量级别 O(α(n)),其中 α 表示反阿克曼函数,增长极其缓慢几乎可视为常数。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值