省队集训Ⅱ-Day7

Day7

又是只整了一道题的一天

Tree

给一棵点带权的树, 分成两个集合 AB, A 的权值是集合中 ij 的祖先, 且 Vi>Vj 的无序点对数加 ij 无直系关系 (不存在一个点是另一个点的祖先) 的无序点对数加集合中点的深度之和 (根深为 0), B 的权值是集合中 ij 的祖先, 满足 Vi<Vj 的无需点对数.

考场上以为写出正解, 每个节点开了三棵可持久化线段树合并, 最后不会维护, 弃了正解.

然后发现貌似每个答案都是由上一个答案的基础上再从 A 中拿一个点放入 B 的, 事实证明赌对了, 可以贪心, 写了个 n2 的暴力, 痛苦收场:

unsigned n, m, Vmax(0), A, B, Standard;
long long Ans(0), Distur(0), AnsDou(0);
char flg(0);
struct Edge;
struct Node {
  char Deleted;
  Edge *Fst;
  Node *Fa;
  unsigned Val, Dep, Size, Contri, Ace, DeceRo, DeceLe;
  inline const char operator<(const Node &x) const{
    return this->Contri < x.Contri;
  }
}N[500005];
struct Edge {
  Node *To;
  Edge *Nxt;
}E[1000005], *CntE(E);
priority_queue<Node> Q;
inline void Link(Node *x, Node *y) {
  (++CntE)->Nxt = x->Fst;
  x->Fst = CntE;
  CntE->To = y;
} 
void DFS(Node *x) {
  register Edge *Sid(x->Fst);
  x->Size = 1;
  while (Sid) {
    if(Sid->To != x->Fa) {
      Sid->To->Dep = x->Dep + 1;
      Sid->To->Fa = x;
      DFS(Sid->To);
      x->Size += Sid->To->Size;
    }
    Sid = Sid->Nxt;
  }
  return;
}
unsigned DFS1(Node *x) {
  register Edge *Sid(x->Fst);
  register unsigned TmpDe((x->Val < Standard) ? 1 : 0);
  while (Sid) {
    if(Sid->To != x->Fa)
      TmpDe += DFS1(Sid->To);
    Sid = Sid->Nxt;
  }
  return TmpDe;
}
int main() {
  n = RD();
  for (register unsigned i(1); i <= n; ++i)
    N[i].Val = RD(), Vmax = (Vmax < N[i].Val) ? N[i].Val : Vmax;
  for (register unsigned i(1); i < n; ++i) {
    A = RD(), B = RD();
    Link(N + A, N + B);
    Link(N + B, N + A);
  }
  A = N[1].Val, N[1].Dep = 0;
  DFS(N + 1);
  for (register unsigned i(1); i <= n; ++i) {
    Standard = N[i].Val;
    Ans += (N[i].DeceRo = DFS1(N + i));
    AnsDou += n + N[i].Dep - N[i].Size;
  }
  Ans += (AnsDou >> 1);
  printf("%lld\n", Ans);
  for (register unsigned i(1); i <= n; ++i) { // i th
    register Node *Choice, *now;
    register long long Con;
    Distur = -0x3f3f3f3f3f3f3f3f;
    for (register unsigned j(1); j <= n; ++j) { // Del j
      if(N[j].Deleted) {continue;}
      now = N[j].Fa;
      Con = (long long)n - i + 1 - N[j].Size + N[j].DeceRo - N[j].DeceLe;
      while (now) {
        if(!now->Deleted) if (now->Val > N[j].Val) ++Con;
        else {++Con; if (now->Val < N[j].Val) --Con;}
        now = now->Fa;
      }
      if(Con > Distur) Distur = Con, Choice = N + j;
    }
    Choice->Deleted = 1;
    now = Choice->Fa;
    while (now) {
      --(now->Size);
      if (now->Val < Choice->Val) ++(now->DeceLe);
      if (now->Val > Choice->Val) --(now->DeceRo);
      now = now->Fa;
    }
    Ans -= Distur;
    printf("%lld\n", Ans);
  }
  return 0;
}

下面是正解:

首先发现这些 B 中计入权值的点对数, 在所有点对中的补就是 A 中计入权值的点数.

先讨论节点权值互不相同的情况, 考虑一个点 xA 放到 B 中, 它对答案的贡献是:

iB[ix,Vi<Vx]+iB[xi,Vi>Vx]iA[ix,Vi>Vx]iA[xi,Vx>Vi]iA[ix,xi]Depi

因为

|A|1=iA[ix,Vi>Vx]+iA[xi,Vx>Vi]+iA[ix,Vi<Vx]+iA[xi,Vx<Vi]+iA[ix,xi]

所以贡献变成了:

iB[ix,Vi<Vx]+iB[xi,Vi>Vx]Depi|A|+iA[ix,Vi<Vx]+iA[xi,Vx<Vi]

因为 A, B 关于全部节点互补, 所以转化为:

i[ix,Vi<Vx]+i[xi,Vi>Vx]Depi|A|+1

|A| 和选哪个点无关, 而 i[ix,Vi<Vx]+i[xi,Vi>Vx]DepiA, B 的元素无关, 所以可以预处理, 然后贪心选择这个值最小的.

接下来考虑有权值相同的情况. 如果 Vi=Vj, 但是 i, j 互不是对方的祖先, 这时它们的权值相对大小对答案无影响. 不失一般性, 不妨设 ij 的祖先, 这时 k[ki,Vk<Vi] 一定不大于 k[kj,Vk<Vj], k[ik,Vk>Vi] 一定不小于 k[jk,Vk>Vj]. 而造成 k[kj,Vk<Vj]k[ki,Vk<Vi] 差值的, 一定是 i, j 中间的点, 数量不超过 DepjDepi1 个, 所以, i 的贡献一定不如 j 的小, 所以选 j.

考虑权值相同, 则:

|A|1=iA[ix,Vi>Vx]+iA[xi,Vx>Vi]+iA[ix,Vi<Vx]+iA[xi,Vx<Vi]+iA[ix,Vi=Vx]+iA[xi,Vx=Vi]+iA[ix,xi]

贡献值变成:

i[ix,Vi<Vx]+i[xi,Vi>Vx]+iA[ix,Vi=Vx]+iA[xi,Vx=Vi]Depi|A|+1

但是前面说了, 权值相同的祖先和后代, 先选后代更优, 也就是说, 在权值相等时, 一定是先选后代, 再选祖先, 所以不能存在 ij 的祖先, Vi=Vj, 且 iB, jA 的情况.

所以对于 xA, 所有的 Vi=Vx, 只要 ix 的祖先, 一定有 iA;

对于 xB, 所有的 Vi=Vx, 只要 xi 的祖先, 一定有 iB

所以 x 放入 B 的时候, 有:

iA[xi,Vx=Vi]=0iA[ix,Vi=Vx]=i[ix,Vi=Vx]

所以 x 的贡献值就是:

i[ix,Vi<Vx]+i[xi,Vi>Vx]+i[ix,Vi=Vx]Depi|A|+1=i[ix,ViVx]+i[xi,Vi>Vx]Depi|A|+1

上代码:

#define Lowbit(x) ((x)&(-(x)))
unsigned n, m, Vmax(0), A, B, FaTr[500005], DeTr[500005];
int Con[500005];
long long Ans(0);
char flg(0);
struct Edge;
struct Node {
  char Deleted;Edge *Fst;Node *Fa;
  unsigned Val, Dep, Size, Ace, Dece;
  int Contri;
  inline const char operator<(const Node &x) const{return this->Contri < x.Contri;}
}N[500005];
struct Edge {Node *To; Edge *Nxt;}E[1000005], *CntE(E);
priority_queue<Node> Q;
inline void Link(Node *x, Node *y) {(++CntE)->Nxt = x->Fst, x->Fst = CntE, CntE->To = y;}
inline void FaAdd(unsigned Pos) {while (Pos <= Vmax) ++FaTr[Pos], Pos += Lowbit(Pos);}
inline void FaMinu(unsigned Pos) {while (Pos <= Vmax) --FaTr[Pos], Pos += Lowbit(Pos);}
inline int FaQry(unsigned Pos) {
  register int TmpA(0);
  while (Pos) TmpA += FaTr[Pos], Pos -= Lowbit(Pos);
  return TmpA;
}
inline void DeAdd(unsigned Pos) {while (Pos <= Vmax) ++DeTr[Pos], Pos += Lowbit(Pos);}
inline void DeMinu(unsigned Pos) {while (Pos <= Vmax) --DeTr[Pos], Pos += Lowbit(Pos);}
inline int DeQry(unsigned Pos) {
  register int TmpA(0);
  while (Pos) TmpA += DeTr[Pos], Pos -= Lowbit(Pos);
  return TmpA;
}
void DFS(Node *x) {
  register Edge *Sid(x->Fst);
  x->Size = 1, x->Contri = FaQry(x->Val), FaAdd(x->Val), DeAdd(x->Val);
  Ans += ((FaQry(Vmax) - FaQry(x->Val)) << 1);
  register unsigned TmpSum(DeQry(Vmax) - DeQry(x->Val));
  while (Sid) {
    if(Sid->To != x->Fa) {
      Sid->To->Dep = x->Dep + 1, Sid->To->Fa = x;
      DFS(Sid->To);
      x->Size += Sid->To->Size, Ans += n + Sid->To->Dep - Sid->To->Size;
    }
    Sid = Sid->Nxt;
  }
  x->Contri = x->Contri + (DeQry(Vmax) - DeQry(x->Val) - TmpSum) - x->Dep;
  FaMinu(x->Val);
  return;
}
int main() {
  n = RD();
  for (register unsigned i(1); i <= n; ++i)
    N[i].Val = RD(), Vmax = (Vmax < N[i].Val) ? N[i].Val : Vmax;
  for (register unsigned i(1); i < n; ++i)
    A = RD(), B = RD(), Link(N + A, N + B), Link(N + B, N + A);
  A = N[1].Val, N[1].Dep = 0;
  DFS(N + 1);
  Ans = Ans >> 1;
  printf("%lld\n", Ans);
  for (register unsigned i(1); i <= n; ++i) Con[i] = N[i].Contri;
  sort(Con + 1, Con + n + 1);
  for (register int i(1); i <= n; ++i) Ans += Con[i], Ans -= (int)n - i, printf("%lld\n", Ans);
  return 0;
}
posted @   Wild_Donkey  阅读(52)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
历史上的今天:
2020-06-06 U68862 奶牛滑迷宫
点击右上角即可分享
微信分享提示