基于分块思想的暴力

基于分块思想的暴力

题面:基于分块思想的暴力


前言:本文讲述的方法并非正解。但是能拿到 \(90\) pts。

虽然并非正解,但是这种处理单点修改的题目大多可以用这种思想将暴力优化到 \(O(n\sqrt{m})\) 的复杂度。骗分可能很好用


​ 对于操作 \(1\) 我们容易发现,树是一个二分图,所以一共只有两组。相当于是固定的集合修改,又因为一个集合被修改两次相当于没修改。所以我们可以同时维护 \(4\) 颗树,分别表示 \(2\) 个集合有无被修改奇数次。

​ 对于操作 \(2\) 是单点修改,下文再提解决方案。

​ 对于操作 \(3\)。我们可以定义 \(f_i=(id_i-id_{fa})\times size_{i}\)。那么所求的答案就是从查询点 \(u\) 到根上的 \(f\) 的总和。如果没有操作 \(2\) 的话我们可以用前缀和统计,然后查询是 \(O(1)\) 的。

​ 那么对于操作 \(2\) 我们该怎么处理呢。

​ 实际上,由于操作 \(2\) 是单点修改,查询的时候只要可以 \(O(1)\) 统计这个修改对总答案的贡献,再结合之前预处理的 \(f\) 前缀和我们就可以在 \(O(cnt)\) 的时间内查询答案。这里我们可以运用分块的思想,将操作 \(2\) 存下来。如果操作 \(2\) 的个数达到了 \(O(\sqrt{m})\) 个,那么我就就 \(O(n)\) 重构前缀和,然后清空当前存储的操作 \(2\)。那么这样我们就能保证复杂度是 \(O(n\times\sqrt{m})\) 的了。

​ 至于 \(O(1)\) 统计答案的贡献,我们可以存储一次修改的位置,修改前的颜色,修改后的颜色。记录修改后的颜色是因为一个点可能被多次修改,所以直接调用数组并不是我们修改后的值。记录这两个我们在查询答案时就让答案减去修改前颜色的贡献再加上修改后颜色的贡献。复杂度是 \(O(\sqrt{m})\) 的。至于求 LCA,这里不能用倍增,需要用到 \(O(n\log n)-O(1)\) 的 st 表求 LCA 的方式,不然复杂度会退化成 \(O(n\times\sqrt{m}\times\log n)\)。

​ 那么总复杂度是 \(4n\sqrt{m}\) 。(就是因为这个 \(4\),被卡常了

代码如下(\(90\) pts):

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN =1e5+5;
bool Sunny;
int b[MAXN][3],cnt,color[MAXN],n,m,now[2],a[MAXN],dep[MAXN];
int st[MAXN<<1][22],idx[MAXN],totdfn,dfn[MAXN<<1],lg[MAXN<<1];
ll sum[MAXN][2][2],siz[MAXN][2][2];
struct E
{
	int to,pre;
}e[MAXN<<1];
int head[MAXN],tot_E;
void add(int u,int v)
{
	e[++tot_E]=E{v,head[u]};
	head[u]=tot_E;
}
bool Small;
void init()
{
	for(int i=1;i<=totdfn;++i) st[i][0]=dfn[i];
	for(int j=1;j<=lg[totdfn];++j)
	{
		for(int i=1;i<=totdfn;++i)
		{
			if(i+(1<<j)-1>totdfn) break;
			if(dep[st[i][j-1]]<dep[st[i+(1<<(j-1))][j-1]]) st[i][j]=st[i][j-1];
			else st[i][j]=st[i+(1<<(j-1))][j-1];
		}
	}
}
inline int LCA(int l,int r)
{
	l=idx[l],r=idx[r];
	if(l>r) swap(l,r);
	int k=lg[r-l+1]-1;
	if(dep[st[l][k]]<dep[st[r-(1<<k)+1][k]]) return st[l][k];
	else return st[r-(1<<k)+1][k];
}
void pre_dfs(int p,int fa,int d)
{
	color[p]=d;dep[p]=dep[fa]+1;
	dfn[++totdfn]=p;idx[p]=totdfn;
	for(int i=head[p];i;i=e[i].pre)
	{
		int to=e[i].to;
		if(to==fa) continue;
		pre_dfs(to,p,d^1);
		dfn[++totdfn]=p;
	}
}
void Get_siz(int p,int fa)
{
	siz[p][0][0]=a[p];
	siz[p][0][1]=color[p]?a[p]^1:a[p];
	siz[p][1][0]=color[p]?a[p]:a[p]^1;
	siz[p][1][1]=a[p]^1;
	for(int i=head[p];i;i=e[i].pre)
	{
		int to=e[i].to;
		if(to==fa) continue;
		Get_siz(to,p);
		siz[p][0][0]+=siz[to][0][0];
		siz[p][0][1]+=siz[to][0][1];
		siz[p][1][0]+=siz[to][1][0];
		siz[p][1][1]+=siz[to][1][1];
	}
}
void dfs(int p,int fa)
{
	sum[p][0][0]=sum[fa][0][0]-fa*siz[p][0][0]+p*siz[p][0][0];
	
	sum[p][1][0]=sum[fa][1][0]-fa*siz[p][1][0]+p*siz[p][1][0];
	
	sum[p][0][1]=sum[fa][0][1]-fa*siz[p][0][1]+p*siz[p][0][1];
	
	sum[p][1][1]=sum[fa][1][1]-fa*siz[p][1][1]+p*siz[p][1][1];
	for(int i=head[p];i;i=e[i].pre)
	{
		int to=e[i].to;
		if(to==fa) continue;
		dfs(to,p);
	}
}
void remake()
{
	Get_siz(1,0);
	dfs(1,0);
	cnt=0;
}
ll Q(int x)
{
	ll ans=sum[x][now[0]][now[1]];
	for(int i=1;i<=cnt;++i)
	{
		int pos=b[i][0],pre=b[i][1],val=b[i][2];
		if(pre^now[color[pos]]==1) ans-=LCA(pos,x);
		if(val^now[color[pos]]==1) ans+=LCA(pos,x);
	}
	return ans;
}
int main()
{
//	freopen("tree.in","r",stdin);
//	freopen("tree.out","w",stdout);
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n*2;++i) lg[i]=lg[i-1]+(1<<lg[i-1]==i);
	for(int i=1;i<=n;++i)
		scanf("%d",&a[i]);
	for(int i=1;i<n;++i)
	{
		int u,v;
		scanf("%d %d",&u,&v);
		add(u,v);add(v,u);
	}
	pre_dfs(1,0,0);
	Get_siz(1,0);
	dfs(1,0);
	init();
	int len=sqrt(m);
	for(int i=1;i<=m;++i)
	{
		if(cnt==len) remake();
		int opt,x;
		scanf("%d %d",&opt,&x);
		if(opt==1) now[color[x]^1]^=1;
		else if(opt==2)
		{
			++cnt;
			b[cnt][0]=x;b[cnt][1]=a[x];b[cnt][2]=a[x]^1;
			a[x]^=1;
		}
		else printf("%lld\n",Q(x));
	}
	return 0;
}
上一篇:题解 洛谷 P3236 [HNOI2014]画框


下一篇:数据结构-栈与队列--中缀转为后缀表达式