兄弟你说得对但是能过 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;
}
为什么要把这些东西写在神秘做题记录本上啊。

浙公网安备 33010602011771号