兄弟你说得对但是能过 T2 的没一个人不过 T1。

兄弟你说得对但是能过 T2 的没一个人不过 T1。

T1 T4 诈骗懒得喷。

T2 T3 还是有意思的。

T2

场上看到 T2 就觉得比较有前途啊于是猛猛写猛猛调最后还真调出来了。

没有拆式子,在树链上硬算贡献。

首先当然要离线然后时间倒流,把最后的树建出来,然后开始返回去一个个删点。

重链剖分,建好线段树。

重剖要记这些东西:

ll vm[N];// 当前点子树上点的数量 - 重儿子子树点的数量(下面有用)
struct treed{
  int dfn;// dfn
  int hs; // 重儿子
  ll siz; // 算上自己的子树大小
  int fa; // 父亲
  int dep;// 深度
  int top;// 链头
}td[N];

现在在最后的树上你就可以把答案 \(O(n)\) 算出来了,以下代码:

ll ansa;// 当前原式的结果
void dfs3(int u,int fa){
  ll fsum=1;// 其实是个前缀和
  ansa+=nw[u];
  for(int v:edge[u]){
    if(v==fa)continue;
    dfs3(v,u);
    ansa+=fsum*td[v].siz*nw[u];
    fsum+=td[v].siz;
  }
}

接下来建线段树,维护这些东西:

struct segt{
  int l,r;
  int siz;// 链上所有点子树大小之和
  int lzt;// 区间减子树大小用的懒标记
  
  ll v;//子树总大小-重儿子子树大小,这个只用单点改没有懒标记
}t[N*4];

接下来是怎么算贡献。

现在要删点 \(u\),我们在链上从 \(u\) 跳到 \(1\)

在一个链上,对于链上每个点 \(v\),因为我们维护了每个点的 子树总大小-重儿子子树大小,剩下的所有点与要删的点的 LCA 就一定是 \(v\) 了(因为我们现在在重链上)。于是要删去贡献也就是所有点的 v 乘上他们的 点权。

因为删点,因此还要给这个链上的所有点的子树大小都减一。但是对于 子树总大小-重儿子子树大小 ,由于删的点在重链上因此是不变的。

但是跳上另一个链的时候就有些不同,因为此时我们从轻链上来的(设这个链的最头上的节点为 \(top\)),因此另一个链最底下的那个点(也就是 td[top].fa,下面设为 \(fa\))的,子树总大小-重儿子子树大小 就要减一了。这里是单点减。

然后我们需要单独处理这个点的贡献,也就是 \(fa\) 的子树大小减去 \(top\) 的子树大小乘上 \(top\) 的点权。给答案减去这个就行了。

于是在上面我们统计链的时候最底下的点就不用统计了,这里要注意。

然后跳的时候麻烦的是有很多特殊情况要处理,比如说一次就跳到 \(1\) 的、重链只有自己的,等等。

于是代码比较难写。注释比较详细了。

Show me the code
#define psb push_back
#define mkp make_pair
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){
  ll x=0,f=1;
  char c=getchar();
  while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
  while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
  return x*f;
}

ll ansa=0;

const int N=1e5+2000;
int n,m,r,pi;ll nw[N];ll vm[N];
struct treed{
  int dfn;int hs;ll siz;int fa;int dep;int top;
}td[N];
int rf[N];
vector<int> edge[N];
void dfs1(int u,int fa){
  td[u].fa=fa;
  td[u].dep=td[fa].dep+1;
  td[u].siz=1;
  int maxsub=-1;
  for(int i=0;i<edge[u].size();i++){
    int v=edge[u][i];
    if(v==fa)continue;
    dfs1(v,u);
    td[u].siz+=td[v].siz;
    if(td[v].siz>maxsub){
      maxsub=td[v].siz;
      td[u].hs=v;
    }
  }
  return ;
}
int idx=0;
void dfs2(int u,int top){
  td[u].dfn=++idx;
  rf[idx]=u;
  td[u].top=top;
  vm[u]=td[u].siz-td[td[u].hs].siz;
  if(!td[u].hs)return ;
  dfs2(td[u].hs,top);
  for(int i=0;i<edge[u].size();i++){
    int v=edge[u][i];
    if(v==td[u].fa||v==td[u].hs)continue;
    dfs2(v,v);
  }
}
void dfs3(int u,int fa){
  ll fsum=1;
  ansa+=nw[u];
  for(int v:edge[u]){
    if(v==fa)continue;
    dfs3(v,u);
    ansa+=fsum*td[v].siz*nw[u];
    fsum+=td[v].siz;
  }
}
struct segt{
  int l,r;int siz;int lzt;
  
  ll v;//子树总大小-重儿子子树大小
}t[N*4];
void pushup(int u){
  int ls=u*2;
  int rs=u*2+1;
  t[u].v=(t[ls].v+t[rs].v);
  t[u].siz=(t[ls].siz+t[rs].siz);
  return ;
}
void build(int p,int l,int r){
  t[p].l=l;t[p].r=r;t[p].lzt=0;
  if(l==r){
    t[p].v=vm[rf[l]]*nw[rf[l]];
    t[p].siz=td[rf[l]].siz;
    return ;
  }
  int mid=l+r>>1;
  build(p*2,l,mid);
  build(p*2+1,mid+1,r);
  pushup(p);
}
void pushdown(int p){
  if(t[p].lzt==0)return ;
  int k=t[p].lzt;
  t[p].lzt=0;

  int ls=p*2;
  int rs=p*2+1;
  t[ls].lzt+=k;
  t[rs].lzt+=k;
  t[ls].siz+=(t[ls].r-t[ls].l+1)*k;
  t[rs].siz+=(t[rs].r-t[rs].r+1)*k;
    
  return ;
}
void decl(int p,int pos){
  if(t[p].l==t[p].r&&t[p].l==pos){
    t[p].v-=nw[rf[pos]];return;
  }
  pushdown(p); 
  int mid=t[p].l+t[p].r>>1;
  if(pos<=mid)decl(p*2,pos);
  if(mid<pos)decl(p*2+1,pos);

  pushup(p);
  return ;
}
void univd(int p,int l,int r){
  if(l<=t[p].l&&t[p].r<=r){
    t[p].siz-=(t[p].r-t[p].l+1);
    t[p].lzt--;
    return ;
  }
  pushdown(p); 
  int mid=t[p].l+t[p].r>>1;
  if(l<=mid)univd(p*2,l,r);
  if(mid<r )univd(p*2+1,l,r);
  pushup(p);
  return ;
}
ll query(int p,int l,int r){
  if(l<=t[p].l&&t[p].r<=r)return t[p].v;
  pushdown(p);
  int mid=t[p].l+t[p].r>>1;
  ll sub=0;
  if(l<=mid){sub+=query(p*2,l,r);}
  if(mid<r){sub+=query(p*2+1,l,r);}
  return sub;
}
ll pquery(int p,int pos){
  if(t[p].l==t[p].r&&t[p].l==pos)return t[p].siz;
  pushdown(p);
  int mid=t[p].l+t[p].r>>1;
  ll sub=0;
  if(pos<=mid){sub=pquery(p*2,pos);}
  if(mid<pos){sub=pquery(p*2+1,pos);}
  return sub;
}
ll cquery(int x,int y){

  ll sub=0;int sv=x;
  // sub 维护减小的值
  if(td[x].top==1){// 直接到 1 的情况
  	univd(1,td[td[x].top].dfn,td[x].dfn);
  	if(td[td[x].fa].dfn>=td[td[x].top].dfn)
      sub+=query(1,td[td[x].top].dfn,td[x].dfn);
  	return sub;
	}
	bool f=0;
	while(td[x].top==x&&x){// 重链只有自己一个点
		if(!f)sub+=nw[x];
		f=1;
		sv=x;
		univd(1,td[td[x].top].dfn,td[x].dfn);
    x=td[td[x].top].fa;
    if(x==0)break;
    decl(1,td[x].dfn);
    sub+=(pquery(1,td[x].dfn)-pquery(1,td[sv].dfn)-1)*nw[x];
	}
	if(x==0)return sub;
  while(td[x].top!=td[y].top){
  	if(!f)sub+=nw[x];
  	f=1;
    if(td[td[x].top].dep<=td[td[y].top].dep)swap(x,y);// 其实这句话没什么用
    univd(1,td[td[x].top].dfn,td[x].dfn);// 全局减子树大小
    if(td[td[x].fa].dfn>=td[td[x].top].dfn)// 判断下防止 re
      sub+=query(1,td[td[x].top].dfn,td[td[x].fa].dfn);// td[x].fa 跳过最底下节点,因为已经处理过了(在上一轮 while 里) 
    sv=td[x].top;
    x=td[td[x].top].fa;
    decl(1,td[x].dfn);// 去处理下一个链最下面那个点
    sub+=(pquery(1,td[x].dfn)-pquery(1,td[sv].dfn)-1)*nw[x];
  }
  univd(1,td[td[x].top].dfn,td[x].dfn);// 最后一个到 1 的链的收尾工作
  if(td[td[x].fa].dfn>=td[td[x].top].dfn)
      sub+=query(1,td[td[x].top].dfn,td[td[x].fa].dfn);

  return sub;
}

struct inpt{
  int op;
  int w;int fa;int nid;
}inp[N];

stack<ll> ansl;
int main(){
  
  // freopen("tree.in","r",stdin);
  // freopen("tree.out","w",stdout);

  int q,w1;cin>>q>>w1;
  nw[1]=w1;int cnt=1;
  for(int i=1;i<=q;i++){
    int op;op=rd;
    if(op==1){
      int x,fff;
      x=rd;fff=rd;
      cnt++;
      nw[cnt]=x;
      edge[fff].push_back(cnt);
      edge[cnt].push_back(fff);
      inp[i].op=1;
      inp[i].w=x;inp[i].fa=fff;inp[i].nid=cnt;
    }
    if(op==2){
      inp[i].op=2;
    }
  }
  r=1;
  dfs1(r,0);dfs2(r,r);build(1,1,cnt);
  dfs3(r,0);// 先算出来最后的答案
   for(int i=q;i>=1;i--){
     int op=inp[i].op;
     if(op==1){
       ansa-=cquery(inp[i].nid,1);// 在一点点减去
     }
     else{
       ansl.push(ansa);
     }
   }
   while(ansl.size()){
     cout<<ansl.top()<<'\n';
     ansl.pop();
   }

  return 0;
}

T3

不会根号科技被 hxy 学长实践了/jk

小技巧就是维护子段是否连续为 \(1\),我们只需要关心每个颜色及其两边的颜色是 \(0\) 还是 \(1\) 就好了。

于是这样我们有结论:改变一种颜色的 \(01\) 影响子段的数量为这个颜色位的数量 减去 相邻颜色为 \(1\) 的位置的数量。

这样我们把相邻且相同颜色压成同一个点,改变 \(01\) 的维护是 \(O(1)\) 的,但是查询是 \(O(n)\) 的。

考虑我们可不可以对每种颜色维护那个东西,这样虽然查询为 \(O(1)\),但是修改是 \(O(n)\) 的。

上不去下不来,卡在这里了。

这时候有个小技巧:根号分治。

我们把点分成两种,设颜色数为 \(k\),把相邻颜色大于 \(\sqrt{k}\) 的点分一组,称为大颜色,另一组称为小颜色。

然后你会发现大颜色最多只有 \(\sqrt{k}\) 种,因为再多序列就装不下了。

我们对于所有大颜色,维护一个贡献数组。

于是,如果我们变换小颜色的值,就暴力统计更改带来的贡献,因为相邻的点不超过 \(\sqrt{k}\),因此时间复杂度就是 \(O(\sqrt{k})\) 的。

同时,由于小颜色变化,我们可以修改小颜色相邻的大颜色的贡献。

这样,对一个小颜色的修改及求值是 \(O(\sqrt{k})\) 的。

对于大点,我们也暴力修改相邻的大颜色的贡献,是 \(O(\sqrt{k})\) 的。

但是此时我们不用统计贡献了,贡献数组里已经存好了。

于是对于大颜色的修改及求值也是 \(O(\sqrt{k})\) 的。

于是我们用 \(O(q\sqrt{k})\) 的时间复杂度通过了这题。

有意思的小技巧。

Show me the code
#define psb push_back
#define mkp make_pair
#define ls p<<1
#define rs (p<<1)+1
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){
  ll x=0,f=1;
  char c=getchar();
  while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
  while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
  return x*f;
}
const int N=2e5+5;
int c[N];
int num[N];
int op[N];
vector<int> e[N];
int ans=0;
bool st[N];
int t;
vector<int> ql[N],qn[N];
int main(){
  
  int n,q,k;cin>>n>>q>>k;
  t=sqrt(n);
  for(int i=1;i<=n;i++){cin>>c[i];}
  for(int i=1,j=1,b=1;i<=n;i=j){
    j=i;
    while(j<=n&&c[i]==c[j])j++;
    num[c[i]]++;
    e[c[i]].push_back(c[i-1]);
    if(j<=n)e[c[i]].push_back(c[j]);
  }
  for(int i=1;i<=k;i++){
    map<int,int> mp;
    for(auto v:e[i]){
      if(e[v].size()>=t)mp[v]++;
    }
    for(auto v:mp){
      ql[i].push_back(v.first);
      qn[i].push_back(v.second);
    }
  }
  for(int i=1;i<=q;i++){
    int c;cin>>c;
    if(st[c]==1){
      st[c]^=1;
      for(int j=0;j<ql[c].size();j++){
        op[ql[c][j]]-=qn[c][j];
      }
      int re=0;
      if(e[c].size()<t){
        for(auto v:e[c]){
          re+=(st[v]==1?1:0);
        }
      }
      else re=op[c];
      ans-=(num[c]-re);
    }
    else{
      st[c]^=1;
      for(int j=0;j<ql[c].size();j++){
        op[ql[c][j]]+=qn[c][j];
      }
      int re=0;
      if(e[c].size()<t){
        for(auto v:e[c]){
          re+=(st[v]==1?1:0);
        }
      }
      else re=op[c];
      ans+=(num[c]-re);
    }
    cout<<ans<<'\n';
  }
   
  return 0;
}

为什么要把这些东西写在神秘做题记录本上啊。

posted @ 2025-07-17 16:48  hm2ns  阅读(48)  评论(3)    收藏  举报