蝴蝶の好题分享 题解

CF1707E

题意:

\(f([l,r])=[\min\limits_{i=l}^ra_i,\max\limits_{i=l}^ra_i]\),保证 \(1\le a_i\le n\)

\(q\) 次询问,每次给 \([l,r]\),求最小的 \(k\) 使得 \(f^k([l,r])=[1,n]\) 或报告无解。

一个性质是若有有交的区间 \(A,B\) 和区间 \(C\) 满足 \(A\cup B=C\),那么 \(f^k(C)=f^k(A)\cup f^k(B)\)

证明:

考虑数学归纳。

那么就相当于要证如果 \(A,B\) 有交且 \(A\cup B=C\),那么 \(f(A),f(B)\) 有交且 \(f(A)\cup f(B)=f(C)\)

这两个结论都是显然的,那么就证毕了。

然后你就可以倍增+ST 表求出 \(f^{2^a}([x,x+2^b-1])\),然后就做完了。

时间复杂度 \(O(n\log^2n+q\log n)\),显然能过。

#include<bits/stdc++.h>
using namespace std;
const int N=100005;
int n,q,a[N];
struct node{int l,r;}st[20][20][N],que;
node merge(node a,node b){return (node){min(a.l,b.l),max(a.r,b.r)};}
node query(int x,node t)
{
	int l=t.l,r=t.r,p=__lg(r-l+1);
	return merge(st[x][p][l],st[x][p][r-(1<<p)+1]);
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>q;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=n;i++)st[0][0][i]=(node){a[i],a[i]};
	for(int i=1;i<20;i++)for(int j=1;j<=n-(1<<i)+1;j++)st[0][i][j]=merge(st[0][i-1][j],st[0][i-1][j+(1<<i-1)]);
	for(int i=1;i<20;i++)for(int j=1;j<20;j++)for(int k=1;k<=n-(1<<j)+1;k++)st[i][j][k]=query(i-1,st[i-1][j][k]);
	for(int i=1,ans;i<=q;i++)
	{
		cin>>que.l>>que.r;ans=0;
		if(que.l==1&&que.r==n){cout<<"0\n";continue;}
		for(int j=19;~j;j--)
		{
			node x=query(j,que);
			if(x.l>1||x.r<n){que=x;ans+=(1<<j);}
		}
		node x=query(0,que);
		if(x.l>1||x.r<n)cout<<"-1\n";
		else cout<<ans+1<<'\n';
	}
}

P8261

题意:

有若干个任何颜色的点,若两个点颜色相同则这两个点之间有一条以这两个点为端点的线段,每次询问给你一条直线,问有多少个线段完全在直线的下方。

线段完全在直线的下方就是两个点都在直线的下方,所以我们直接假装没有线段,问题转化为设给定线段下的点的数量为 \(c_i\),求 \(\frac{c_i^2+c_i}{2}\)

那么就是要求两个东西,一次方和二次方。

我们注意到线段下的点十分甚至九分的不好做,所以把给定的直线变成点,点变成直线,这个是一个很经典的转换。

先看一次方。注意到对于一次方,每条直线(即原先的点,以后不做说明,均为变换后)颜色并没有用。

这个东西应该叫 UOJ NOI Round4 Day2 T2 己酸集合。

首先我们有一个 \(O((n^2+q)\log n)\) 的做法:把 \(n^2\) 个交点都求出来然后排序一遍就能找出每一个点线段的顺序,查询的时候对 \(x\) 二分找到顺序再对 \(y\) 二分就可以得到答案了。

这并过不去,问题在于 \(n\)\(q\) 量级差太多了,那么我们考虑根号平衡,对每 \(B\) 个线段做这个东西做 \(\frac{n}{B}\) 次。

那么时间复杂度为 \(O(\frac{n}{B}(B^2+q)\log n)\),取 \(B=\sqrt{q}\) 就可以得到时间复杂度为 \(O(n\sqrt{q}\log n)\)

然后一次方就做完了。

二次方的话,你直接对每一种颜色做一个己酸集合不就做完了吗,然而你发现假了,因为如果每种点的颜色互不相同就炸了,分块有一个上取整。

所以我们只能对直线数量 \(\ge\sqrt{q}\) 的颜色做这个东西。

如果直线数量的颜色 \(<\sqrt{q}\),考虑对于每一种颜色,加到集合里,直到集合里 \(\ge\sqrt{q}\) 再做一次暴力就行了。

注意到由于每次加的都是小集合,所以每次集合里的大小是 \(O(\sqrt{q})\)

但是多个颜色可能并不好很好的处理,所以我们要对算法改进。

考虑到每个交点只会交换,所以前缀的改变量是 \(O(1)\) 的,所以我们预处理前缀的答案,然后就做完了。

不会基于数据随机的单根号。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+5,B=700;
int n,m,pos[N],cnt[N],pre[N],suf[N],rkp[N],rks[N],num[N],ans[N];
pair<int,int>p[N];
vector<pair<int,int> >vec[N],tmp;
struct point{int x,y,c;}s[N];
bool operator<(point a,point b){return a.x<b.x||(a.x==b.x&&a.y<b.y);}
vector<point>sp;
struct frac{int x,y;};
bool operator<(frac a,frac b){return a.x*b.y<a.y*b.x;}
bool operator<=(frac a,frac b){return a.x*b.y<=a.y*b.x;}
bool operator>(frac a,frac b){return a.x*b.y>a.y*b.x;}
bool operator>=(frac a,frac b){return a.x*b.y>=a.y*b.x;}
bool operator==(frac a,frac b){return a.x*b.y==a.y*b.x;}
struct line{int i,j;frac k;}e[N<<2];
bool operator<(line a,line b){return a.k<b.k||(a.k==b.k&&(a.i<b.i||(a.i==b.i&&a.j<b.j)));}
struct que{int a,b,c,id,dir;frac k;}q[N];
bool operator<(que a,que b){return a.k<b.k;}
int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
void big(const vector<pair<int,int> >&a)
{
	int l=a.size(),idx=0;
	for(int i=0;i<l;i++)p[i+1]=a[i];
	sort(p+1,p+l+1);
	for(int i=1;i<=l;i++){pos[i]=i;for(int j=i+1;j<=l;j++)if(p[i].first!=p[j].first)e[++idx]=(line){i,j,(frac){p[j].second-p[i].second,p[j].first-p[i].first}};}
	sort(e+1,e+idx+1);
	for(int i=1,j=1;i<=m;i++)
	{
		while(j<=idx&&e[j].k<=q[i].k)
		{
			swap(p[pos[e[j].i]],p[pos[e[j].j]]);
			swap(pos[e[j].i],pos[e[j].j]);
			j++;
		}
		if(q[i].dir==0)
		{
			int L=1,R=l,Ans=0,mid;
			while(L<=R)
			{
				mid=L+R>>1;
				if(q[i].a*p[mid].first+q[i].b*p[mid].second+q[i].c<0){Ans=mid;L=mid+1;}
				else R=mid-1;
			}
			cnt[q[i].id]+=Ans;
		}
		else
		{
			int L=1,R=l,Ans=l+1,mid;
			while(L<=R)
			{
				mid=L+R>>1;
				if(q[i].a*p[mid].first+q[i].b*p[mid].second+q[i].c<0){Ans=mid;R=mid-1;}
				else L=mid+1;
			}
			cnt[q[i].id]+=l-Ans+1;
		}
	}
}
void jsjh(int c)
{
	int sz=0;tmp.clear();
	for(pair<int,int> x:vec[c])
	{
		tmp.push_back(x);sz++;
		if(sz==B){big(tmp);tmp.clear();sz=0;}
	}
	if(sz)big(tmp);
	for(int i=1;i<=m;i++){ans[i]+=cnt[i]*cnt[i];cnt[i]=0;}
}
void small(const vector<point>&a)
{
	int l=a.size(),idx=0;
	for(int i=0;i<l;i++)s[i+1]=a[i];
	sort(s+1,s+l+1);
	for(int i=1;i<=l;i++){pos[i]=i;for(int j=i+1;j<=l;j++)if(s[i].x!=s[j].x)e[++idx]=(line){i,j,(frac){s[j].y-s[i].y,s[j].x-s[i].x}};}
	sort(e+1,e+idx+1);
	for(int i=1;i<=l;i++){rkp[i]=++num[s[i].c];pre[i]=pre[i-1]+rkp[i]*2-1;}
	for(int i=1;i<=l;i++)num[s[i].c]=0;
	for(int i=l;i>=1;i--){rks[i]=++num[s[i].c];suf[i]=suf[i+1]+rks[i]*2-1;}
	for(int i=l;i>=1;i--)num[s[i].c]=0;
	for(int i=1,j=1;i<=m;i++)
	{
		while(j<=idx&&e[j].k<=q[i].k)
		{
			int p1=pos[e[j].i],p2=pos[e[j].j];
			if(p1>p2)swap(p1,p2);
			if(s[p1].c!=s[p2].c)
			{
				swap(rkp[p1],rkp[p2]);
				pre[p1]=pre[p1-1]+rkp[p1]*2-1;
				pre[p2]=pre[p1]+rkp[p2]*2-1;
				swap(rks[p1],rks[p2]);
				suf[p2]=suf[p2+1]+rks[p2]*2-1;
				suf[p1]=suf[p2]+rks[p1]*2-1;
			}
			swap(s[p1],s[p2]);
			swap(pos[e[j].i],pos[e[j].j]);
			j++;
		}
		if(q[i].dir==0)
		{
			int L=1,R=l,Ans=0,mid;
			while(L<=R)
			{
				mid=L+R>>1;
				if(q[i].a*s[mid].x+q[i].b*s[mid].y+q[i].c<0){Ans=mid;L=mid+1;}
				else R=mid-1;
			}
			if(Ans)ans[q[i].id]+=pre[Ans];
		}
		else
		{
			int L=1,R=l,Ans=l+1,mid;
			while(L<=R)
			{
				mid=L+R>>1;
				if(q[i].a*s[mid].x+q[i].b*s[mid].y+q[i].c<0){Ans=mid;R=mid-1;}
				else L=mid+1;
			}
			if(Ans)ans[q[i].id]+=suf[Ans];
		}
	}
	for(int i=1;i<=l;i++)rkp[i]=rks[i]=pre[i]=suf[i]=0;
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	n=read();m=read();
	for(int i=1,x,y,c;i<=n;i++)
	{
		x=read();y=read();c=read();
		vec[c].push_back(make_pair(x,y));
	}
	for(int i=1,x,y,c;i<=m;i++)
	{
		x=read();y=read();c=read();
		q[i].a=x;q[i].b=y;q[i].c=c;q[i].id=i;
		if(y<0){q[i].k=(frac){x,-y};q[i].dir=1;}
		if(y>0){q[i].k=(frac){-x,y};q[i].dir=0;}
		if(y==0){q[i].k=(frac){-1,0};q[i].dir=x<0?1:0;}
	}
	sort(q+1,q+m+1);
	for(int i=1;i<=n;i++)if(vec[i].size()>=B)jsjh(i);
	for(int i=1;i<=n;i++)if(vec[i].size()<B)
	{
		for(pair<int,int> x:vec[i])sp.push_back((point){x.first,x.second,i});
		if(sp.size()>=B){small(sp);sp.clear();}
	}
	if(sp.size())small(sp);
	for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
}

P9247

题意:

\(n\) 个队列,初始为空。

\(i\) 个队列最多能放 \(a_i\) 个。任意时刻如果第 \(i\) 个队列里的元素超过 \(a_i\) 个,执行若干次 pop 操作直到只剩 \(a_i\) 个元素。

每次操作要求对于一个区间内的队列 push 一个值,然后询问所有队列内共有多少个不同的元素。

神仙数据结构题。

首先把询问离线下来处理。

你发现这个东西相当于问:什么时候,这个询问造成的影响有效。

显然这是一个区间。称其为有效区间。

那么答案就是枚举每个颜色的贡献,把颜色的所有有效区间并起来,然后把并起来得到的东西那块 +1(其实就是若干个区间加),差分就行了。

问题在于这个区间怎么求。

考虑分块。

对于整块,考虑双指针扫一遍。那么就是要求对于每个块,区间加,求区间 max。

有个想法是直接拍一个树状数组上去,但是这样太蠢了,整个序列只长为根号。

所以区间加可以考虑如果是整块加直接打标记,否则暴力重构即可。

注意到这样时间复杂度是对的,因为你考虑这样相当于进行正常的分块操作。

对于散块,考虑扫描线扫一遍,每次扫到一个队列,维护一个树状数组,看看这个队列是不是某个查询的起点,是的话把编号加进去,是不是某个查询的终点加 1,是的话把编号删除。

然后再枚举所有包含这个点的位置,树状数组二分就行了。

稍微调一下块长可以得到总复杂度为 \(O(n\sqrt{n\log n})\)

代码写挂了,懒得调了,摆烂了。

CF1588F

题意:

给你一个环森林,有三种操作:编号区间求和,环加,断开/合并两个环。

好厉害的题。

先考虑没有操作 3 怎么做。

这部分我自己想出来了,感觉自己还是很有水平的!

考虑根号重构,对于每根号个操作:

修改的话,直接对环打标记就行了。

查询的话,注意到只有根号个环打上了标记,没打上标记的直接按着上个操作块最后结果的前缀和做,然后枚举每一个打上标记的环,二分计算这个环有多少个点在区间中即可。

这样会带个老哥,考虑去掉。

考虑直接把修改的那些环的每个点做一个前缀和,然后查询的时候就可以直接差分就行了。

然而这样空间带了个根号,但是你注意到你差分所需要的位置也只有根号种,所以可以只存这些需要的结点,空间就可以做到线性了。

然后现在操作 3 回来了。

这块太困难了,拜读了一下大手子做法。

还是根号重构。

注意到我们可以把每个环细分成一个一个连续段,使得每个连续段的结构都在这个操作块中不会改变。

然后直接把环作为基本单位变成段作为基本单位就行了。

这个东西我想过,乍一看复杂度肯定不对,于是我就弃思路了。

但是事实上是对的,为啥呢?

以下是我证了半个小时的结论,不知道为啥题解都没说。

首先一个显然的一件事情是段的总个数是环数加根号的。

那么考虑对于最开始的每个环,选出一个“代言段”。

那么非代言段总共只有根号个,修改的自然不超过根号。

对于代言段,假设一次操作加了 \(x\) 个代言段,那么一定存在 \(x-1\) 次代言段合并到目前这个环。

那么就是 \(x\) 次操作动了 \(x\) 个代言段,均摊下来就是每次操作只动常数个代言段,那么所以动的代言段是根号的。

所以总段数是根号的。复杂度自然正确。

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5,B=450;
int n,q,idx,tick,p[N],o[N],op[N],x[N],y[N],rt[N],rd[N],id[N],vis[N],cnt[N],pre[1003][1003];
long long a[N],sum[N],tag[N];
int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
void getrt(int v,int u){for(;!rt[v];v=o[v]){rt[v]=u;}}
void update(int u,long long w)
{
	for(;!vis[u];u=rt[p[u]]){vis[u]=1;tag[u]+=w;}
	for(;vis[u];u=rt[p[u]])vis[u]=0;
}
int main()
{
	n=read();
	for(int i=1;i<=n;i++)a[i]=read();
	for(int i=1;i<=n;i++){p[i]=read();o[p[i]]=i;}
	q=read();
	for(int i=1;i<=q;i++){op[i]=read();x[i]=read();y[i]=read();}
	for(int l=1,r=B;l<=q;l+=B,r+=B)
	{
		r=min(r,q);
		for(int i=0;i<=n;i++)
		{
			cnt[i]=rt[i]=tag[i]=id[i]=0;
			if(i)sum[i]=sum[i-1]+a[i]; 
		}
		idx=0;
		for(int i=l;i<=r;i++)
		{
			
			if(op[i]>1)rt[x[i]]=x[i];
			else
			{
				if(!id[x[i]-1])id[x[i]-1]=++idx;
				if(!id[y[i]])id[y[i]]=++idx;
			}
			if(op[i]>2)rt[y[i]]=y[i]; 
		}
		idx=0;
		for(int i=1;i<=n;i++)if(rt[i]==i)
		{
			rt[i]=0;rd[++idx]=i;
			getrt(i,i);
		}
		for(int i=0;i<=n;i++)
		{
			cnt[rt[i]]++;
			if(id[i])for(int j=1;j<=idx;j++)pre[id[i]][j]=cnt[rd[j]];
		}
		for(int i=l;i<=r;i++)
		{
			if(op[i]==1)
			{
				long long ans=sum[y[i]]-sum[x[i]-1];
				for(int j=1;j<=idx;j++)ans+=tag[rd[j]]*(pre[id[y[i]]][j]-pre[id[x[i]-1]][j]);
				printf("%lld\n",ans);
			}
			if(op[i]==2)update(x[i],y[i]);
			if(op[i]==3)
			{
				swap(p[x[i]],p[y[i]]);
				swap(o[p[x[i]]],o[p[y[i]]]);
			}
		}
		for(int i=1;i<=n;i++)a[i]+=tag[rt[i]];
	}
}

P8479

题意:

给你一棵树,点有点权,有如下两种操作:

0.毛毛虫加;

1.毛毛虫最大子段和。

其中毛毛虫定义如下,毛毛虫是一个队列,有两个端点 \((u,v)\),每次从 \(u\) 沿着树上唯一的路径走到 \(v\),每走到一个点 \(x\),先把 \(x\) 加入队列,再按着优先级顺序把 \(x\) 的不在 \(u\)\(v\) 的路径里的邻接点加入队列。

注意按着如上定义,毛毛虫是有顺序的。

大手子题。参考了出题人题解。

这个题本身适应范围很窄,但是也许可以告诉我们——做题的时候,不必拘泥于重链剖分、长链剖分这两种特定的剖分形式,在看到题目有关树的查询比较怪的时候,可以试着自己胡一个剖分方式,万一做出来了呢?

首先看这道题的前置题目:P7735 [NOI2021] 轻重边。

这道题的题意如下:

给你一棵树,边有边权(01),有如下两种操作。

1.毛毛虫赋值为 0,接着链赋值为 1;

2.链求和。

其中毛毛虫定义如下,毛毛虫是一个边集,有两个端点 \((u,v)\)。对于所有在 \(u\)\(v\) 路径上的点 \(x\),所有与 \(x\) 相邻的边都在边集中。

首先是一个 naive 的 trick,边权转点权,不赘述。

然后你发现这基本和原题一样,只有一个区别:毛毛虫是没有顺序的。

考虑我们如何维护一个毛毛虫呢?

这就引申出来一个算法——毛毛虫剖分。

甚至你可以把标号的时候的搜索叫做毛毛虫优先搜索(cfs),把最后的标号叫做 cfn,不过还是习惯是 dfs 和 dfn。

考虑如下标号方式。

当你遍历到点 \(u\) 的时候:

  1. 如果 \(u\) 没被标号,为其标号;
  2. 如果 \(u\) 是重链顶,从上到下遍历这条重链,每遍历到一个点,为其所有轻儿子标号;
  3. 先递归 \(u\) 的重儿子,再递归 \(u\) 的轻儿子。

考虑这个标号顺序的优越性:

  1. 对于每个点,它的轻儿子标号连续;
  2. 对于每个重链,所有与重链相邻的点标号连续;
  3. 对于每个重链,除链头外标号连续。

那么这个标号就可以很好的解决 P7735 这个问题了。

看似 P8479 也可以用这个算法解决,然而这个做法有一个致命问题:虽然虫身和虫足都是 \(\log\) 段的,但是把虫身和虫足按着顺序揉在一起后不一定是只有 \(\log\) 段了。

怎么办呢?注意到这道题不需要链查询,所以可以失去链的 \(\log\) 段性来保证本题定义的毛毛虫的 \(\log\) 段性。

具体的,我们可以考虑如下方法来改进我们的毛毛虫剖分,由于我感觉官方题解起的毛毛虫剖分 2.0 很难听,我们把他叫做顺序毛毛虫剖分。

我们首先标号树根。

然后当你遍历到点 \(u\) 时:

  1. 如果 \(u\) 是重链顶,从上到下遍历这条重链,每遍历到一个点,如果这个点不是 \(u\),为这个点标号,然后按着顺序为这个点的轻儿子编号;
  2. 递归遍历 \(u\) 的轻儿子。

其实就是毛毛虫剖分之后把重链和重链相邻点揉在一起。

这样,我们牺牲了路径修改查询的可能性,来换取了序毛毛虫修改查询的可能性。

然而,事情并不简单。

考虑一个直上直下的毛毛虫,这个是可以维护的。

但是考虑一个绕来绕去的毛毛虫,我们一般的处理方式是先维护 \(u\)\(lca\),再维护 \(lca\)\(v\),也就是 \(v\)\(lca\) 的逆序。

这个时候问题来了:遍历儿子的时候是按着优先级遍历的,你逆序一下优先级不就反了吗?

所以我们还有维护一个逆优先级的标号,两个标号才是完整的顺序毛毛虫剖分:

我们首先标号树根。

然后当你遍历到点 \(u\) 时:

  1. 如果 \(u\) 是重链顶,从上到下遍历这条重链,每遍历到一个点,按着逆序为这个点的轻儿子编号,然后如果这个点不是 \(u\),为这个点标号;
  2. 递归遍历 \(u\) 的轻儿子。

那么就做完了。

欢迎收看我写的大史山:

#include<bits/stdc++.h>
#define int long long
using namespace std;
namespace Fread
{
	const int SIZE=1<<21;
	char buf[SIZE],*S,*T;
	inline char getchar()
	{
	    if(S==T)
		{
	        T=(S=buf)+fread(buf,1,SIZE,stdin);
	        if(S==T)return '\n';
	    }
	    return *S++;
	}
}
namespace Fwrite
{
	const int SIZE=1<<21;
	char buf[SIZE],*S=buf,*T=buf+SIZE;
	inline void flush()
	{
	    fwrite(buf,1,S-buf,stdout);
	    S=buf;
	}
	inline void putchar(char c)
	{
	    *S++=c;
	    if(S==T)flush();
	}
	struct NTR
	{
	    ~NTR(){flush();}
	}ztr;
}
#ifdef ONLINE_JUDGE
#define getchar Fread::getchar
#define putchar Fwrite::putchar
#endif
namespace Fastio
{
	struct Reader
	{
	    template<typename T>
	    Reader& operator>>(T& x)
		{
	        char c=getchar();
	        T f=1;
	        while(c<'0'||c>'9')
			{
	            if(c=='-')f=-1;
	            c=getchar();
	        }
	        x=0;
	        while(c>='0'&&c<='9')
			{
	            x=x*10+(c-'0');
	            c=getchar();
	        }
	        x*=f;
	        return *this;
	    }
	    Reader& operator>>(char& c)
		{
	        c = getchar();
	        while (c==' '||c=='\n')c=getchar();
	        return *this;
	    }
	    Reader& operator>>(char* str)
		{
	        int len = 0;
	        char c = getchar();
	        while (c==' '||c=='\n') c = getchar();
	        while (c!=' '&&c!='\n'&&c!='\r')
			{
	            str[len++] = c;
	            c = getchar();
	        }
	        str[len] = '\0';
	        return *this;
	    }
	    Reader(){}
	}cin;
	const char endl='\n';
	struct Writer
	{
	    template<typename T>
	    Writer& operator<<(T x)
		{
	        if(x==0){putchar('0');return *this;}
	        if(x<0){putchar('-');x=-x;}
	        static int sta[45];
	        int top=0;
	        while(x){sta[++top]=x%10;x/=10;}
	        while(top){putchar(sta[top]+'0');--top;}
	        return *this;
	    }
	    Writer& operator<<(char c)
		{
	        putchar(c);
	        return *this;
	    }
	    Writer& operator<<(char* str)
		{
	        int cur=0;
	        while(str[cur])putchar(str[cur++]);
	        return *this;
	    }
	    Writer& operator<<(const char* str)
		{
	        int cur = 0;
	        while(str[cur])putchar(str[cur++]);
	        return *this;
	    }
	    Writer(){}
	}cout;
}
#define cin Fastio::cin
#define cout Fastio::cout
#define endl Fastio::endl
const int N=1e5+5;
int t,n,q,idx[2]={1,1},a[N],fa[N],dep[N],sz[N],son[N],top[N],dfn[N][2],rk[N][2],fst[N][2],lst[N][2],sp[N][2];
vector<int>g[N];
struct node{int sum,l,r,mx;};
node operator+(node x,node y)
{
	node z;
	z.sum=x.sum+y.sum;
	z.l=max(x.l,x.sum+y.l);
	z.r=max(x.r+y.sum,y.r);
	z.mx=max(max(x.mx,y.mx),x.r+y.l);
	return z;
}
struct seg
{
	node p[N<<2];int tag[N<<2],lz[N<<2];
	void pushup(int u){p[u]=p[u<<1]+p[u<<1|1];}
	void pushlazy(int u,int l,int r,int w)
	{
		tag[u]=1;lz[u]=w;p[u].sum=(r-l+1)*w;
		p[u].l=p[u].r=p[u].mx=(r-l+1)*max(w,0ll);
	}
	void pushdown(int u,int l,int r)
	{
		if(tag[u])
		{
			int mid=l+r>>1;
			pushlazy(u<<1,l,mid,lz[u]);
			pushlazy(u<<1|1,mid+1,r,lz[u]);
			tag[u]=0;
		}
	}
	void build(int u,int l,int r,int id)
	{
		tag[u]=0;
		if(l==r)
		{
			int w=a[rk[l][id]],c=max(w,0ll);
			p[u]=(node){w,c,c,c};
			return;
		}
		int mid=l+r>>1;
		build(u<<1,l,mid,id);
		build(u<<1|1,mid+1,r,id);
		pushup(u);
	}
	void update(int u,int l,int r,int L,int R,int w)
	{
		if(L>R||r<L||l>R)return;
		if(L<=l&&r<=R)
		{
			pushlazy(u,l,r,w);
			return;
		}
		pushdown(u,l,r);int mid=l+r>>1;
		update(u<<1,l,mid,L,R,w);
		update(u<<1|1,mid+1,r,L,R,w);
		pushup(u);
	}
	node query(int u,int l,int r,int L,int R)
	{
		if(L>R||r<L||l>R)return (node){0,0,0,0};
		if(L<=l&&r<=R)return p[u];
		pushdown(u,l,r);int mid=l+r>>1;
		return query(u<<1,l,mid,L,R)+query(u<<1|1,mid+1,r,L,R);
	}
}tr[2];
void dfs1(int u)
{
	sz[u]=1;dep[u]=dep[fa[u]]+1;
	for(int v:g[u])
	{
		dfs1(v);sz[u]+=sz[v];
		if(sz[son[u]]<sz[v])son[u]=v;
	}
}
void dfs2(int u)
{
	fst[u][0]=idx[0]+1;
	if(!top[u])dfn[u][0]=++idx[0];
	for(int v:g[u])
	{
		if(v==son[u])sp[u][0]=idx[0];
		else dfn[v][0]=++idx[0];
	}
	lst[u][0]=idx[0];
	if(son[u])dfs2(son[u]);
}
void dfs3(int u)
{
	fst[u][1]=idx[1]+1;
	reverse(g[u].begin(),g[u].end());
	for(int v:g[u])
	{
		if(v==son[u])sp[u][1]=idx[1];
		else dfn[v][1]=++idx[1];
	}
	reverse(g[u].begin(),g[u].end());
	if(!top[u])dfn[u][1]=++idx[1];
	lst[u][1]=idx[1];
	if(son[u])dfs3(son[u]);
}
void dfs4(int u,int tp)
{
	top[u]=tp;
	if(u==tp){dfs2(u);dfs3(u);}
	if(son[u])dfs4(son[u],tp);
	for(int v:g[u])if(v!=son[u])dfs4(v,v);
}
int lca(int u,int v)
{
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]])v=fa[top[v]];
		else u=fa[top[u]];
	}
	if(dep[u]<dep[v])return u;
	return v;
}
void update(int u,int v,int w,int id)
{
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]])swap(u,v);
		tr[id].update(1,1,n,fst[top[u]][id],lst[u][id],w);
		if(son[u])tr[id].update(1,1,n,dfn[son[u]][id],dfn[son[u]][id],w);
		tr[id].update(1,1,n,dfn[top[u]][id],dfn[top[u]][id],w);
		u=fa[top[u]];
	}
	if(dep[u]<dep[v])swap(u,v);
	tr[id].update(1,1,n,fst[v][id],lst[u][id],w);
	if(son[u])tr[id].update(1,1,n,dfn[son[u]][id],dfn[son[u]][id],w);
	if(v==top[v])tr[id].update(1,1,n,dfn[v][id],dfn[v][id],w);
	if(fa[v])tr[id].update(1,1,n,dfn[fa[v]][id],dfn[fa[v]][id],w);
}
void append(node &res,int u,int p,int id)
{
	if(dfn[p][id]<sp[u][id])
	{
		res=tr[id].query(1,1,n,sp[u][id]+1,lst[u][id])+res;
		if(son[u])res=tr[id].query(1,1,n,dfn[son[u]][id],dfn[son[u]][id])+res;
		res=tr[id].query(1,1,n,max(dfn[p][id]+1,fst[u][id]),sp[u][id])+res;
		res=tr[id].query(1,1,n,fst[u][id],dfn[p][id]-1)+res;
	}
	else
	{
		res=tr[id].query(1,1,n,max(dfn[p][id]+1,fst[u][id]),lst[u][id])+res;
		res=tr[id].query(1,1,n,max(sp[u][id]+1,fst[u][id]),dfn[p][id]-1)+res;
		if(son[u])res=tr[id].query(1,1,n,dfn[son[u]][id],dfn[son[u]][id])+res;
		res=tr[id].query(1,1,n,fst[u][id],min(sp[u][id],dfn[p][id]-1))+res;
	}
}
pair<node,int> climb(int u,int v,int id)
{
	int p=0;
	node res=(node){0,0,0,0};
	while(top[u]!=top[v])
	{
		if(u!=top[u])
		{
			append(res,u,p,id);
			res=tr[id].query(1,1,n,lst[top[u]][id]+1,fst[u][id]-1)+res;
			u=top[u];
			if(id)
			{
				res=tr[1].query(1,1,n,dfn[u][1],dfn[u][1])+res;
				res=tr[1].query(1,1,n,fst[u][1],lst[u][1])+res;
			}
			else
			{
				res=tr[0].query(1,1,n,fst[u][0],lst[u][0])+res;
				res=tr[0].query(1,1,n,dfn[u][0],dfn[u][0])+res;
			}
		}
		else if(id)
		{
			res=tr[1].query(1,1,n,dfn[u][1],dfn[u][1])+res;
			append(res,u,p,1);
		}
		else
		{
			append(res,u,p,0);
			res=tr[0].query(1,1,n,dfn[u][0],dfn[u][0])+res;
		}
		p=u;
		u=fa[u];
	}
	if(u!=v)
	{
		append(res,u,p,id);
		p=son[v];
		res=tr[id].query(1,1,n,fst[p][id],fst[u][id]-1)+res;
	}
	return make_pair(res,p);
}
int query(int u,int v)
{
	int LCA=lca(u,v);
	pair<node,int> pu=climb(u,LCA,1),pv=climb(v,LCA,0);
	node res=pu.first;
	swap(res.l,res.r);
	res=res+tr[0].query(1,1,n,dfn[LCA][0],dfn[LCA][0]);
	if(fa[LCA])res=res+tr[0].query(1,1,n,dfn[fa[LCA]][0],dfn[fa[LCA]][0]);
	vector<pair<int,int> >tmp;
	if(pu.second&&pu.second!=son[LCA])tmp.push_back(make_pair(dfn[pu.second][0],0));
	if(pv.second&&pv.second!=son[LCA])tmp.push_back(make_pair(dfn[pv.second][0],0));
	if(pu.second!=son[LCA]&&pv.second!=son[LCA])tmp.push_back(make_pair(sp[LCA][0],2));
	tmp.push_back(make_pair(lst[LCA][0],1));
	sort(tmp.begin(),tmp.end());
	int las=fst[LCA][0]+(LCA!=top[LCA]);
	for(pair<int,int> &p:tmp)
	{
		res=res+tr[0].query(1,1,n,las,p.first-!p.second);
		if(p.first==sp[LCA][0]&&p.second==2)res=res+tr[0].query(1,1,n,dfn[son[LCA]][0],dfn[son[LCA]][0]);
		las=p.first+1;
	}
	res=res+pv.first;
	return res.mx;
}
signed main()
{
	cin>>t>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=2;i<=n;i++)
	{
		cin>>fa[i];
		g[fa[i]].push_back(i);
	}
	dfs1(1);dfn[1][0]=dfn[1][1]=1;dfs4(1,1);
	for(int i=1;i<=n;i++)rk[dfn[i][0]][0]=rk[dfn[i][1]][1]=i;
	tr[0].build(1,1,n,0);tr[1].build(1,1,n,1);
	cin>>q;
	for(int i=1,op,u,v,k;i<=q;i++)
	{
		cin>>op>>u>>v;
		if(op)cout<<query(u,v)<<'\n';
		else
		{
			cin>>k;
			update(u,v,k,0);
			update(u,v,k,1);
		}
	}
}

CF1990F

题意:

单点修改,区间最长连续多边形区间。

多边形区间是以区间里的所有数作为边长可以构成一个多边形的区间。

比较有趣的题,不过竟然只有 *2800,感觉怎么也得 *3200 以上吧?

一个很平凡的结论是多边形区间等价于区间最大值小于区间和的一半。

先考虑如果只查询全局怎么做。

先钦定最大值,注意到在最大值固定的情况下和越大越好。那么一个最大值对应的区间是固定的,事实上可以注意到这就是笛卡尔树上这个节点的子树。

那么考虑建出笛卡尔树,然后直接枚举所有节点看看子树对应的区间合不合法,然后对所有合法的区间长度取 max 就行了。

现在考虑有单点修改了,查询还是区间的,考虑线段树。

首先需要解决的是合并问题,可以使用 FHQ-Treap 的方式合并。由于合并之后原先的数据还是要用上的,所以可持久化笛卡尔树合并即可。

但是合并是 \(O(dep)\) 的,笛卡尔树深度很容易被卡到 \(O(n)\)

但是你注意到,如果有一个节点的子树合法,它的子树节点的子树就不用判了,反正子树里面肯定比它小。

所以考虑直接递归到合法区间然后 return 就行了。

接下来我们证明,这个树的深度是 \(O(\log\sum a_i)\)

证明:

考虑如果在这个节点没有 return,那么左子树加上右子树再加上最大值是总和。

最大值要大于总和的一半,所以左子树加上右子树小于总数的一半,所以左子树和右子树各小于总数的一半。

也就是说每次总数至少减少一半,那么深度就是 \(O(\log\sum a_i)\)。证毕!

那么合并就是 \(O(\log\sum a_i)\) 的了。

时间复杂度 \(O((n+q\log n)\log\sum a_i)\)。8s 随便过。但是这个复杂度真的对吗?

精细分析一下 build 复杂度可以得到 build 是 \(O(n\log\log\sum a_i)\) 的。

做法就是考虑最后 \(\log\log\sum a_i\) 层,每层加起来最多有 \(n\) 个点,所以这块是 \(O(n\log\log\sum a_i)\) 的。

考虑其他层,共有 \(2^{\log_2\frac{n}{\log a_i}}=\frac{n}{\log a_i}\) 个结点,每个结点有最多 \(\log a_i\) 个点,所以这块是 \(O(n)\) 的。

所以时间复杂度事实上是 \(O(n\log\log\sum a_i+q\log n\log\sum a_i)\),十分优秀。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+5,M=1e7;
int t,n,q,idx,cnt,stk[M];
ll a[N];
vector<int>vec;
struct node{int sz,lc,rc,ans;ll w,sum;}dt[M];
struct Node{int rt;vector<int>id;}c[N<<2];
struct CartesiaTree
{
	int newnode()
	{
		int id;
		if(cnt)id=stk[cnt--];else id=++idx;
		dt[id]=(node){0,0,0,0,0,0};
		return id;
	}
	void pushup(int u)
	{
		dt[u].sz=dt[dt[u].lc].sz+dt[dt[u].rc].sz+1;
		dt[u].sum=dt[dt[u].lc].sum+dt[dt[u].rc].sum+dt[u].w;
		dt[u].ans=max(dt[dt[u].lc].ans,dt[dt[u].rc].ans);
	}
	int merge(int u,int v)
	{
		int x=newnode();
		vec.push_back(x);
		if(!u||!v)
		{
			dt[x]=dt[u+v];
			return x;
		}
		if(max(dt[u].w,dt[v].w)*2<dt[u].sum+dt[v].sum&&dt[u].sz+dt[v].sz>=3)
		{
			dt[x].sz=dt[x].ans=dt[u].sz+dt[v].sz;
			dt[x].w=dt[u].w;dt[x].sum=dt[u].sum+dt[v].sum;
			return x;
		}
		if(dt[u].w>dt[v].w)
		{
			dt[x]=dt[u];
			dt[x].rc=merge(dt[x].rc,v);
		}
		else
		{
			dt[x]=dt[v];
			dt[x].lc=merge(u,dt[x].lc);
		}
		pushup(x);
		return x;
	}
}tr;
void newnode(int u,ll w)
{
	int x=c[u].rt=tr.newnode();
	dt[x].sz=1;dt[x].sum=dt[x].w=w;
}
void pushup(int u)
{
	vec.clear();
	c[u].rt=tr.merge(c[u<<1].rt,c[u<<1|1].rt);
	c[u].id=vec;
}
void build(int u,int l,int r)
{
	if(l==r){newnode(u,a[l]);return;}
	int mid=l+r>>1;
	build(u<<1,l,mid);
	build(u<<1|1,mid+1,r);
	pushup(u);
}
void update(int u,int l,int r,int p,ll w)
{
	if(p<l||p>r)return;
	if(l==r)
	{
		stk[++cnt]=c[u].rt;
		newnode(u,w);return;
	}
	int mid=l+r>>1;
	update(u<<1,l,mid,p,w);
	update(u<<1|1,mid+1,r,p,w);
	for(int x:c[u].id)stk[++cnt]=x;
	pushup(u);
}
int query(int u,int l,int r,int L,int R)
{
	if(r<L||l>R)return 0;
	if(L<=l&&r<=R)return c[u].rt;
	int mid=l+r>>1;
	return tr.merge(query(u<<1,l,mid,L,R),query(u<<1|1,mid+1,r,L,R));
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>t;
	while(t--)
	{
		idx=cnt=0;cin>>n>>q;
		for(int i=1;i<=n;i++)cin>>a[i];
		build(1,1,n); 
		for(int i=1,op,l;i<=q;i++)
		{
			ll r,ans;
			cin>>op>>l>>r;
			if(op==1)
			{
				vec.clear();
				ans=dt[query(1,1,n,l,r)].ans;
				if(ans)cout<<ans<<'\n';
				else cout<<"-1\n";
				for(int x:vec)stk[++cnt]=x;
			}
			else update(1,1,n,l,r);
		}
	}
}

Gym103687K

题意?

给你一个有向图,初始边全为黑色,修改操作是翻转一条边的颜色,查询操作是询问 u 是否可能只经过黑边到达 v。

神人题。How 出题人's mind work?

考虑把询问按时间分块,注意到每次修改涉及到的“关键点”最多有 \(2B\) 个。

每次执行到一个块内,先把白边忽略掉只保留黑边然后缩点。这部分复杂度是 \(O(n+m)\)

维护每个点能到达哪些关键点,bitset 优化即可。这部分的复杂度是 \(O(\frac{(n+m)B}{w})\)

进一步将点缩到只剩关键点,能通过黑边到达的就连边,容易注意到当一个点从黑变白或白变黑之后这个图的更改是很好维护的。每次 bfs 跑一遍就行了,bfs 也要用 bitset 优化。这部分的复杂度是 \(O(\frac{B^3}{w})\)

总时间复杂度 \(O(\frac{q}{B}((n+m)(1+\frac{B}{w})+\frac{B^3}{w}))=O((n+m)q(\frac{1}{w}+\frac{1}{B})+\frac{qB^2}{w})\),取 \(B=\sqrt[3]{(n+m)w}\) 即可最优。

#include<bits/stdc++.h>
using namespace std;
const int N=5e4+4,M=1e5+5,B=213,P=430;
int n,m,q,idx,tp,cnt,p,ans[M],col[M],dfn[N],low[N],stk[N],scc[N],vis[M],cg[M],id[P],rd[N],ct[P][P];
bitset<P>f[N],s[P],t[P],vx,vy;
struct edge{int u,v;}e[M];
struct query{int op,x,y;}que[M];
vector<int>g[N],G[N];
int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
void tarjan(int u)
{
	dfn[u]=low[u]=++idx;
	vis[u]=1;stk[++tp]=u;
	for(int v:g[u])
	{
		if(!dfn[v]){tarjan(v);low[u]=min(low[u],low[v]);}
		else if(vis[v])low[u]=min(low[u],low[v]);
	}
	if(dfn[u]==low[u])
	{
		cnt++;
		for(;;)
		{
			int v=stk[tp--];
			vis[v]=0;scc[v]=cnt;
			if(u==v)break;
		}
	}
}
bool query(int u,int v)
{
	vx.reset();vy.reset();vx[u]=1;
	for(;!vx[v];)
	{
		int p=(vx&~vy)._Find_first();
		if(p==vx.size())return 0;
		vy[p]=1;vx|=s[p]|t[p];
	}
	return 1;
}
void solve(int l,int r)
{
	p=tp=idx=cnt=0;
	for(int i=1;i<=n;i++)
	{
		rd[i]=dfn[i]=scc[i]=vis[i]=0;
		f[i].reset();s[i].reset();t[i].reset();
		g[i].clear();G[i].clear();
	}
	for(int i=1;i<=m;i++)cg[i]=0;
	vector<int>tmp;
	for(int i=l;i<=r;i++)
	{
		if(que[i].op==1)
		{
			tmp.push_back(e[que[i].x].u);
			tmp.push_back(e[que[i].x].v);
			cg[que[i].x]=1;
		}
		else
		{
			tmp.push_back(que[i].x);
			tmp.push_back(que[i].y);
		}
	}
	for(int u:tmp)if(!rd[u]){rd[u]=++p;id[p]=u;}
	for(int i=1;i<=m;i++)if(col[i]&&!cg[i])g[e[i].u].push_back(e[i].v);
	for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i);
	for(int i=1;i<=m;i++)if(col[i]&&!cg[i])G[scc[e[i].u]].push_back(scc[e[i].v]);
	for(int i=1;i<=p;i++)f[scc[id[i]]][i]=1;
	for(int i=1;i<=cnt;i++)for(int j:G[i])f[i]|=f[j];
	for(int i=1;i<=p;i++)for(int j=1;j<=p;j++)s[i][j]=f[scc[id[i]]][j];
	for(int i=1;i<=m;i++)if(col[i]&&cg[i])
	{
		t[rd[e[i].u]][rd[e[i].v]]=1;
		ct[rd[e[i].u]][rd[e[i].v]]++;
	}
	for(int i=l;i<=r;i++)
	{
		if(que[i].op==1)
		{
			col[que[i].x]^=1;
			if(col[que[i].x])
			{
				t[rd[e[que[i].x].u]][rd[e[que[i].x].v]]=1;
				ct[rd[e[que[i].x].u]][rd[e[que[i].x].v]]++;
			}
			else
			{
				ct[rd[e[que[i].x].u]][rd[e[que[i].x].v]]--;
				if(!ct[rd[e[que[i].x].u]][rd[e[que[i].x].v]])t[rd[e[que[i].x].u]][rd[e[que[i].x].v]]=0;
			}
		}
		else ans[i]=query(rd[que[i].x],rd[que[i].y]);
	}
	for(int i=1;i<=m;i++)if(col[i]&&cg[i])ct[rd[e[i].u]][rd[e[i].v]]--;
}
int main()
{
	n=read();m=read();q=read();
	for(int i=1;i<=m;i++){e[i].u=read();e[i].v=read();col[i]=1;}
	for(int i=1;i<=q;i++)
	{
		que[i].op=read();que[i].x=read();
		if(que[i].op==2)que[i].y=read();
	}
	for(int i=1;i<=q;i+=B)solve(i,min(i+B-1,q));
	for(int i=1;i<=q;i++)if(que[i].op==2)printf(ans[i]?"YES\n":"NO\n");
}
posted @ 2025-08-04 09:49  梦幻の蝶  阅读(13)  评论(0)    收藏  举报