[Codeforces773E] [线段树] [找规律] Blog Post Rating

本文介绍了一种通过特定顺序访问博客来最大化其赞数的方法。针对每个人对赞数的期望不同,采用升序访问的方式,并利用权值线段树进行维护,确保每次访问都能达到最优效果。

题意大概是
有一个博客,初始赞数为0,每个人会浏览这篇博客,如果这个人期望的赞数大于这边博客的赞数,他就会赞这篇博客,如果小于这篇博客,就踩一下(博客赞数-1),相同就不操作。询问对于每个i,1~i的人按照一个顺序浏览,求一种顺序使得博客最后的赞数最大,输出这个最大的赞数。

先瞎猜一下,这1~i个人肯定是按照期望的赞数升序访问最优。
然后写个暴力交一下,发现T了,说明这个结论是对的

那么只要维护一下这个序列就可以了。可以用权值线段树维护。
考虑怎么求这个按照这个升序顺序访问能得到的答案。

首先aiai是当前排序后的数列中的第i个,不是原始数列的)是可能有负数的,那么肯定存在某个x,使ax<=x,那么在x的位置,博客的赞数为x,从x+1开始,博客的赞数就会单调不减。
f(i)表示在i位置博客的赞数,那么f(i)=min(f(i1)+1,ai)
展开展开展开发现一个区间[l,r] ,长度为k
f(r)=min(f(l1)+k,al+k1,al+1+k2,......,ar1+1,ar)

那么每个区间维护一下min(al+k1,al+1+k2,......,ar1+1,ar)就好了

#include <cstdio>
#include <iostream>
#include <algorithm>
#define N 500010

using namespace std;

typedef pair<int,int> parii;

int n;
int a[N];
parii b[N];
struct func{
  int mn,tot;
  int val(int x){
    return min(x+tot,mn);
  }
  friend func operator *(func a,func b){
    a.tot+=b.tot;
    a.mn=min(b.mn,a.mn+b.tot);
    return a;
  }
};
struct seg{
  int l,r,mx;
  func f;
}T[N<<3];

void Build(int g,int l,int r){
  T[g].l=l; T[g].r=r; T[g].mx=-(1<<30);
  T[g].f.tot=0; T[g].f.mn=1<<30;
  if(l==r) return ;
  int mid=l+r>>1;
  Build(g<<1,l,mid); Build(g<<1|1,mid+1,r);
  T[g].f=T[g<<1].f*T[g<<1|1].f;
}

void Update(int g,int x,int y){
  T[g].mx=max(T[g].mx,y);
  if(T[g].l==T[g].r){
    T[g].f.tot++; T[g].f.mn=y;
    return ;
  }
  int mid=T[g].l+T[g].r>>1;
  if(x<=mid) Update(g<<1,x,y);
  else Update(g<<1|1,x,y);
  T[g].f=T[g<<1].f*T[g<<1|1].f;
}

int Calc(int g,int l,int r){
  if(T[g].l==l&&T[g].r==r) return T[g].f.tot;
  int mid=T[g].l+T[g].r>>1;
  if(r<=mid) return Calc(g<<1,l,r);
  if(l>mid) return Calc(g<<1|1,l,r);
  return Calc(g<<1,l,mid)+Calc(g<<1|1,mid+1,r);
}

int Find(int g,int l,int r){
  if(T[g].l==l&&T[g].r==r) return T[g].mx;
  int mid=T[g].l+T[g].r>>1;
  if(r<=mid) return Find(g<<1,l,r);
  if(l>mid) return Find(g<<1|1,l,r);
  return max(Find(g<<1,l,mid),Find(g<<1|1,mid+1,r));
}

func Query(int g,int l,int r){
  if(T[g].l==l&&T[g].r==r) return T[g].f;
  int mid=T[g].l+T[g].r>>1;
  if(r<=mid) return Query(g<<1,l,r);
  if(l>mid) return Query(g<<1|1,l,r);
  return Query(g<<1,l,mid)*Query(g<<1|1,mid+1,r);
}

inline bool check(int x){
  int num=Calc(1,1,x),last=Find(1,1,x);
  return last<=-num;
}

int main(){
  freopen("1.in","r",stdin);
  freopen("1.out","w",stdout);
  scanf("%d",&n);
  for(int i=1;i<=n;i++)
    scanf("%d",&a[i]),b[i]=parii(a[i],i);
  sort(b+1,b+1+n);
  for(int i=1;i<=n;i++) a[b[i].second]=i;
  Build(1,1,n);
  for(int i=1;i<=n;i++){
    Update(1,a[i],b[a[i]].first);
    int L=1,R=n,mid,cur=-1;
    while(L<=R) check(mid=L+R>>1)?L=(cur=mid)+1:R=mid-1;
    if(cur==-1) printf("%d\n",Query(1,1,n).val(0));
    else{
      int num=Calc(1,1,cur);
      if(cur==n){ printf("%d\n",-num); continue; }
      printf("%d\n",Query(1,cur+1,n).val(-num));
    }
  }
  return 0;
}
### 关于 CodeForces 892E 的解题思路分析 #### 使用可撤销并查集解决最小生成树中的边集合验证问题 针对给定的无向图以及多个询问,每个询问涉及一组特定的边,并要求判断这组边能否同时存在于某棵最小生成树中。此问题可以通过结合Kruskal算法构建最小生成树的过程来求解,在这一过程中利用到的是按照权重升序排列后的边逐步加入至森林结构之中[^1]。 为了高效处理多次查询而不影响后续操作的结果,引入了带有回溯功能的数据结构——即所谓的“可撤销并查集”。这种特殊形式的并查集允许执行合并(union)的同时记录下每一次变动以便之后能够恢复原状;当完成一次查询判定后即可通过一系列反向动作使数据结构回到初始状态,从而不影响其他独立事件的发生逻辑[^3]。 具体实现方法如下: - 将所有的边依据其权重从小到大排序; - 对每一个询问所涉及到的边也做同样的预处理; - 开始遍历已排序好的全局边列表,每当遇到属于当前待检验询问范围内的边时,则尝试将其纳入现有连通分量内; - 如果发现形成环路则说明该询问无法满足条件; - 同样地,任何不属于当前询问但同样处于相同权值下的其它边也应该被考虑进来以确保最终形成的MST是最优解的一部分; - 完成一轮测试后记得清除所有临时更改使得系统重置为未受干扰的状态准备迎接下一个挑战。 ```cpp #include <bits/stdc++.h> using namespace std; struct Edge { int u, v; }; class DSUWithRollback { public: vector<int> parent, rank, historyParent, historyRank; void init(int n){ parent.resize(n); iota(parent.begin(), parent.end(), 0); // Fill with identity mapping. rank.assign(n, 0); historyParent.clear(); historyRank.clear(); } int findSet(int i) {return (parent[i]==i)?i:(findSet(parent[i]));} bool isSameSet(int i, int j){ return findSet(i)==findSet(j);} void unionSets(int i, int j){ if (!isSameSet(i,j)){ historyParent.push_back(findSet(i)); historyParent.push_back(findSet(j)); historyRank.push_back(rank[findSet(i)]); historyRank.push_back(rank[findSet(j)]); int x=findSet(i), y=findSet(j); if (rank[x]>rank[y]) swap(x,y); parent[x]=y; if (rank[x]==rank[y]) ++rank[y]; } } void rollback(){ while(!historyParent.empty()){ parent[historyParent.back()]=historyParent.back(); historyParent.pop_back(); rank[historyParent.back()] = historyRank.back(); historyParent.pop_back(); historyRank.pop_back(); } } }; ``` 上述代码展示了如何创建一个支持撤销机制的并查集类`DSUWithRollback`,它可以在不破坏原有连接关系的前提下安全地进行节点间的联合与查操作。此外还提供了用于追踪变化历史的方法,方便在必要时候撤消最近的一系列更动。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值