「数据结构 2022/02 」学习记录

[NOI2010] 超级钢琴

给定一个长度为 \(n\) 的序列,你需要选出 \(k\) 个不同的区间,每个区间的长度在 \([L, R]\) 之间。你需要使得选出的所有区间和最大。

其中 \(n,k \le 5 \times 10^5,-10^3 \le a_i \le 10^3\)

做一个前缀和,于是变成了求 \(k\) 个不同的二元组 \((x,y)\)

因为两两不同,所以对于一个 \(x\) 我们只要找满足条件并且 \(y>x\)\(y\) 。显然这是在 \(x\) 右边的连续一段的区间。

对于所有 \(x\) ,把满足条件 \(y\) 中最大贡献的 \(y\) 这个二元组塞进一个堆里,每次取出最大贡献,然后去掉 \(y\) ,原区间分成两区间,再把这两区间的最大贡献的 \(y\)\(x\) 组成二元组塞进堆里。 用 ST 表维护一下区间最大值即可。

复杂度 \(\mathcal{O}((n+k) \log n)\)

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<queue>
using namespace std;
typedef long long ll;
const int N=5e5+5;
int n,m,L,R,a[N],s[N],lg[N],st[N][22];
ll ans;
struct node{
	int x,l,r,id;
	friend bool operator < (const node &u,const node &v){
		return (s[u.x]-s[u.id-1])<(s[v.x]-s[v.id-1]);
	}
};
priority_queue<node> q;
int qmx(int l,int r)
{	int k=lg[r-l+1];
	if(s[st[l][k]]>s[st[r-(1<<k)+1][k]])return st[l][k];
	return st[r-(1<<k)+1][k];
}
int main()
{	scanf("%d%d%d%d",&n,&m,&L,&R);
	lg[0]=-1;
	for(int i=1;i<=n;i++)
	{	scanf("%d",&a[i]);
		s[i]=s[i-1]+a[i],lg[i]=lg[i>>1]+1;
		st[i][0]=i;
	}
	for(int j=1;j<22;j++)
		for(int i=1;i<=n-(1<<j)+1;i++)
		{	if(s[st[i][j-1]]>s[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];
		}
	for(int i=1;i<=n;i++)
	{	int l=i+L-1,r=min(n,i+R-1);
		if(l>n)continue;
		q.push((node){qmx(l,r),l,r,i});
//		printf("%d %d %d %d\n",qmx(l,r),l,r,i);
	}
	for(int k=1;k<=m;k++)
	{	node u=q.top();
		q.pop();
//		printf("k:%d:%d %d %d %d\n",k,u.x,u.l,u.r,u.id);
		ans+=1ll*s[u.x]-s[u.id-1];
		if(u.x>u.l)q.push((node){qmx(u.l,u.x-1),u.l,u.x-1,u.id});
		if(u.r>u.x)q.push((node){qmx(u.x+1,u.r),u.x+1,u.r,u.id});
	}
	printf("%lld\n",ans);
	return 0;
}

[九省联考 2018] IIIDX

给定 \(k\) 和一个长度为 \(n\) 的序列,你需要将其重新排序,满足 \(a_i \ge a_{\left\lfloor \frac{i}{k} \right\rfloor}\)。输出字典序最大的解。

其中 \(n \le 5 \times 10^5\)

题目可转化为,给一棵树,对点附一个点权 \(a_i\) ,满足每个点的点权都小于子树内的点权。然后让这个树的字典树最大。

首先有一个贪心,对 \(a_i\) 排序后从大到小,按子树编号从小到大扔进去。递归处理。

但这个只对 \(a_i\) 互不相同成立,因为如果有相同的,那么可能出现把子树外的点挪到里面,仍然满足条件的情况。

不妨设子树的大小为 \(sz_i\),第 \(i\) 个值出现的次数是 \(cnt_i\) ,设 \(f_x\) 表示大于等于 \(x\) 的还没取的个数。

如果要给第 \(i\) 个点放 \(x\) ,当且仅当 \(f_x \ge sz_i\) 。同时这也意味着对于小于 \(x\)\(f\) 全部减去 \(sz_i-1\)

这样处理后实际上的 \(f_x\)\(\min\limits_{i=1}^x\{ f_i \}\)

考虑从编号小到大分配点权,每次分配最大即可,所以每次找到最大的满足那个条件的即可,然后按照上面的修改修改 \(f\) 。可以用线段树和二分维护。

复杂度 \(\mathcal{O}(n \log n)\)

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=5e5+5;
const int inf=1e9;
int n,a[N],fa[N],sz[N],cnt[N],ans[N],t[N<<2],lz[N<<2];
double k;
bool cmp(int u,int v){return u>v;}
void pushdown(int k)
{	int x=lz[k];lz[k]=0;
	t[k<<1]+=x;lz[k<<1]+=x;
	t[k<<1|1]+=x;lz[k<<1|1]+=x;
}
void build(int k,int l,int r)
{	if(l==r){t[k]=l;return;}
	int mid=(l+r)>>1;
	build(k<<1,l,mid),build(k<<1|1,mid+1,r);
	t[k]=min(t[k<<1],t[k<<1|1]);
}
void modify(int k,int l,int r,int x,int y,int z)
{	if(x<=l&&r<=y)
	{	t[k]+=z,lz[k]+=z;
		return;
	}
	pushdown(k);
	int mid=(l+r)>>1;
	if(x<=mid)modify(k<<1,l,mid,x,y,z);
	if(y>mid)modify(k<<1|1,mid+1,r,x,y,z);
	t[k]=min(t[k<<1],t[k<<1|1]);
}
int query(int k,int l,int r,int x)
{	if(l==r)return ((t[k]>=x)?(l):(l+1));
	pushdown(k);
	int mid=(l+r)>>1;
	if(x<=t[k<<1|1])return query(k<<1,l,mid,x);
	return query(k<<1|1,mid+1,r,x);
}
int main()
{	scanf("%d%lf",&n,&k);
	for(int i=1;i<=n;i++)
	{	scanf("%d",&a[i]);
		fa[i]=(int)floor(i/k),sz[i]=1;
	} 
	sort(a+1,a+1+n,cmp);
	for(int i=n-1;i>=1;i--)
		if(a[i]==a[i+1])cnt[i]=cnt[i+1]+1;
	for(int i=n;i>=1;i--)sz[fa[i]]+=sz[i];
	build(1,1,n);
	for(int i=1;i<=n;i++)
	{	if(fa[i]&&fa[i]!=fa[i-1])modify(1,1,n,ans[fa[i]],n,sz[fa[i]]-1);
		int x=query(1,1,n,sz[i]);
		x+=cnt[x],cnt[x]++;x-=cnt[x]-1;
		ans[i]=x;
		modify(1,1,n,x,n,-sz[i]);
	}
	for(int i=1;i<=n;i++)
		printf("%d ",a[ans[i]]);
	printf("\n");
	return 0;
}

CF1413F Roads and Ramen

给定一棵 \(n\) 个点的树,边权为 \(0/1\)

\(m\) 次操作,每次操作翻转一条边的边权,你需要在每次操作后回答最长的经过偶数个 \(1\) 的路径。

首先有一个性质,最长路径一定是以直径的一端点为起点。

证明的话,假设目前的答案和直径,有交点/没交点,奇/偶,发现从直径一段起点一定会更优。

所以分两种情况然后取最优就可以了。

这个时候变成了根到一个点的路径,所以我们可以用线段树维护 DFS 序,对区间 \([l,r]\) 维护 \(mx_0,mx_1\) 表示最长偶数个 \(0\)\(1\) 。修改的时候修改这条边影响的子树的区间即可。

复杂度 \(\mathcal{O}(n \log n)\)

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
const int N=5e5+5;
struct edge{
	int v,w,nx;
}e[N<<1];
int n,m,ne,rt1,rt2,f[N],deep1[N],deep2[N];
struct tree{
	int rt,tot,rev[N],L[N],R[N],dw[N],deep[N],wp[N];
	void dfs(int u,int ffa)
	{	L[u]=++tot;rev[tot]=u;
		deep[u]=deep[ffa]+1;
		for(int i=f[u];i;i=e[i].nx)
		{	int v=e[i].v;
			if(v==ffa)continue;
			dw[(i+1)>>1]=v,wp[v]=wp[u]^e[i].w;
			dfs(v,u);
		}
		R[u]=tot;
	}
	int mx[N<<2][2],lz[N<<2];
	void pushup(int k)
	{	mx[k][0]=max(mx[k<<1][0],mx[k<<1|1][0]);
		mx[k][1]=max(mx[k<<1][1],mx[k<<1|1][1]);
	}
	void pushdown(int k)
	{	if(!lz[k])return;
		swap(mx[k<<1][0],mx[k<<1][1]),swap(mx[k<<1|1][0],mx[k<<1|1][1]);
		lz[k<<1]^=1;lz[k<<1|1]^=1;
		lz[k]=0;
	}
	void build(int k,int l,int r)
	{	if(l==r)
		{	mx[k][wp[rev[l]]]=deep[rev[l]],lz[k]=0;
			return;
		}
		int mid=(l+r)>>1;
		build(k<<1,l,mid),build(k<<1|1,mid+1,r);
		pushup(k);
	}
	void modify(int k,int l,int r,int x,int y)
	{	if(x<=l&&r<=y)
		{	swap(mx[k][0],mx[k][1]),lz[k]^=1;
			return;
		}
		pushdown(k);
		int mid=(l+r)>>1;
		if(x<=mid)modify(k<<1,l,mid,x,y);
		if(y>mid)modify(k<<1|1,mid+1,r,x,y);
		pushup(k);
	}
	void init()
	{	tot=0;dfs(rt,0);
		build(1,1,n);
	}
}t[2];
void read(int u,int v,int w)
{	e[++ne].v=v;
	e[ne].nx=f[u];
	e[ne].w=w;
	f[u]=ne;
}
void dfs1(int u,int ffa)
{	deep1[u]=deep1[ffa]+1;
	if(deep1[rt1]<deep1[u])rt1=u;
	for(int i=f[u];i;i=e[i].nx)
	{	int v=e[i].v;
		if(v==ffa)continue;
		dfs1(v,u);
	}
}
void dfs2(int u,int ffa)
{	deep2[u]=deep2[ffa]+1;
	if(deep2[rt2]<deep2[u])rt2=u;
	for(int i=f[u];i;i=e[i].nx)
	{	int v=e[i].v;
		if(v==ffa)continue;
		dfs2(v,u);
	}
}
int main()
{	scanf("%d",&n);
	for(int i=1,u,v,w;i<n;i++)
	{	scanf("%d%d%d",&u,&v,&w);
		read(u,v,w),read(v,u,w);
	}
	dfs1(1,0);dfs2(rt1,0);
	t[0].rt=rt1,t[1].rt=rt2;
	t[0].init();t[1].init();
	scanf("%d",&m);
	while(m--)
	{	int x;
		scanf("%d",&x);
		t[0].modify(1,1,n,t[0].L[t[0].dw[x]],t[0].R[t[0].dw[x]]);
		t[1].modify(1,1,n,t[1].L[t[1].dw[x]],t[1].R[t[1].dw[x]]);
		printf("%d\n",max(t[0].mx[1][0],t[1].mx[1][0])-1);
	}
	return 0;
}

CF1320D Reachable Strings

给定一个 01 串。每次询问两个等长的子串,询问是否可以从一个经过数次变换变成另一个。

变换操作的定义是每次选定一个包含 \(110\) 或者 \(011\) 的子段,让 \(110\to 011\) 或者 \(011\to110\)

首先可以根据变化发现,\(0\) 可以两个两个跳,意味着可走同奇偶的位置。

\(00\) 这种是没法互相到达的,说明不能跳过不同奇偶的 \(0\)

于是两个字串,如果他们 \(0\) 个数相同,且每个 \(0\) 奇偶也相同。

所以可以哈希。

[国家集训队]飞飞侠

给定一个 \(n \times m\) 的网格,你可以从 \((i, j)\) 花费 \(a_{i,j}\) 到达所有曼哈顿距离不超过 \(B_{i,j}\) 的点。

求两点之间的最短路。

其中 \(n,m \le 150\)

可以线段树建图优化,但是有更优的。

考虑拆点,设 \(f(i,j,k)\) 表示到 \((i,j)\) 还能走 \(k\) 步。然后把 \((i,j,k)\)\((i-1,j,k-1),(i+1,j,k-1),(i,j-1,k-1),(i,j+1,k-1),(i,j,k-1)\) 连边。

对于边,有 \((i,j,0)\)\((i,j,B_{i,j})\) 连一条长 \(A_{i,j}\) 的边。表示起飞的所消耗,而 \(B_{i,j}\) 可以选择在距离内选任意点。

CF1303G Sum of Prefix Sums

对于一个序列 \(a_1,a_2,...,a_n\),定义它的 “前缀和的和” 为 \(a_1 + (a_1 + a_2) +...+ (a_1 + a_2 + ...+ a_n)\)

给定一棵 n 个点的树,你需要求出 “前缀和的和” 最长的一条路径。

其中 \(n \le 1.5 \times 10^5\)

首先点分治,找到对于一个点的所有路径答案。而显然从叶子出发最优,于是只要存每个叶子对应该点路径的答案即可。

考虑路径怎么合并,设路径 \(x_1,x_2,...,x_k\) 。分成 \(x_1 \sim x_m,x_{m+1} \sim x_k\)

\(l=m,v_1=\sum\limits_{i=1}^m i\cdot a_{x_i},s_2=\sum\limits_{i=m+1}^k a_{x_i},v_2=\sum\limits_{i=m+1}^k (i-m)a_{x_i}\)

合并的答案为 \(v_1+l s_2+v_2\) 。注意在树上做的的时候注意每个部分的根只能算一次。

考虑对这个答案形式进行转化,\((s_2,v_2)\) 表示插入一次函数 \(s_2x+v_x\) 。那么 \((v_1,l)\) 表示查询,询问 \(x=v_1\) 上和他相交的最大 \(y\) 值和 \(l\) 的和。

可以李超线段树维护。复杂度 \(\mathcal{O}(n \log^2 n)\)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
const int N=150000+5;
struct edge{
	int v,nx;
}e[N<<1];
struct node{
	ll u,v1,v2,s,l,f;
}st[N];
struct tree{
	ll ta[N<<2],tb[N<<2];
	void build(int k,int l,int r)
	{	ta[k]=tb[k]=0;
		if(l==r)return;
		int mid=(l+r)>>1;
		build(k<<1,l,mid),build(k<<1|1,mid+1,r);
	}
	void modify(int k,int l,int r,ll x,ll y)
	{	if(ta[k]*l+tb[k]<=x*l+y&&ta[k]*r+tb[k]<=x*r+y)
		{	ta[k]=x,tb[k]=y;
			return;
		}
		if(ta[k]*l+tb[k]>=x*l+y&&ta[k]*r+tb[k]>=x*r+y)return;
		int mid=(l+r)>>1;
		if(ta[k]*mid+tb[k]<x*mid+y)swap(ta[k],x),swap(tb[k],y);
		if(x<ta[k])modify(k<<1,l,mid,x,y);
		else modify(k<<1|1,mid+1,r,x,y);
	}
	ll query(int k,int l,int r,ll x)
	{	ll res=ta[k]*x+tb[k];
		if(l==r)return res;
		int mid=(l+r)>>1;
		if(x<=mid)res=max(res,query(k<<1,l,mid,x));
		else res=max(res,query(k<<1|1,mid+1,r,x));
		return res;
	}
}T;
int read()        
{	int s=0,f=1;        
	char ch=getchar();        
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}        
	while(isdigit(ch)){s=s*10+ch-'0';ch=getchar();}        
	return s*f;      
}
int n,ne,sumn,top,rt,mxd,f[N],sz[N],son[N];
bool vis[N];ll ans,a[N],deep[N];
void read(int u,int v)
{	e[++ne].v=v;
	e[ne].nx=f[u];
	f[u]=ne;
}
void getroot(int u,int ffa)
{	sz[u]=1,son[u]=0;
	for(int i=f[u];i;i=e[i].nx)
	{	int v=e[i].v;
		if(v==ffa||vis[v])continue;
		getroot(v,u);
		sz[u]+=sz[v],son[u]=max(son[u],sz[v]);
	}
	son[u]=max(son[u],sumn-sz[u]);
	if(son[u]<son[rt])rt=u;
}
void dfs(int u,ll v1,ll v2,ll s,ll fff,int ffa)
{	if(u!=rt&&!fff)fff=u;
	deep[u]=deep[ffa]+1,mxd=max(mxd,(int)deep[u]);
	bool is=0;
	for(int i=f[u];i;i=e[i].nx)
	{	int v=e[i].v;
		if(v==ffa||vis[v])continue;
		is=1;
		dfs(v,v1+s+a[v],v2+a[v]*deep[u],s+a[v],fff,u);
	}
	if(!is)st[++top]=(node){u,v1,v2,s-a[rt],deep[u],fff};
}
void solve(int u)
{	vis[u]=1,mxd=0,top=0;
	dfs(u,a[u],0,a[u],0,0);
	st[++top]=(node){u,a[u],0,0,1,0};
	T.build(1,1,mxd);
	st[0].f=st[top+1].f=-1;
	for(int i=1,j;i<=top;)
	{	for(j=i;st[j].f==st[i].f;j++)ans=max(ans,T.query(1,1,mxd,st[j].l)+st[j].v1);
		for(j=i;st[j].f==st[i].f;j++)T.modify(1,1,mxd,st[j].s,st[j].v2);
		i=j;
	}
	T.build(1,1,mxd);
	for(int i=top,j;i>=1;)
	{	for(j=i;st[j].f==st[i].f;j--)ans=max(ans,T.query(1,1,mxd,st[j].l)+st[j].v1);
		for(j=i;st[j].f==st[i].f;j--)T.modify(1,1,mxd,st[j].s,st[j].v2);
		i=j;
	}
	for(int i=f[u];i;i=e[i].nx)
	{	int v=e[i].v;
		if(vis[v])continue;
		sumn=sz[v];rt=0;
		getroot(v,u);
		solve(rt);
	}
}
int main()
{	n=read();
	for(int i=1,u,v;i<n;i++)
	{	u=read(),v=read();
		read(u,v),read(v,u);
	}
	for(int i=1;i<=n;i++)a[i]=read();
	son[0]=n;rt=0;sumn=n;
	getroot(1,0);
	solve(rt);
	printf("%lld\n",ans);
	return 0;
}

[POI2014]HOT-Hotels 加强版

给定一棵 \(n\) 个点的树,求有多少个三元组 \((u, v, w)\),使得它们两两之间的距离相等。

其中 \(n \le 10^5\)

首先答案的形式是,有两个点到达他们的 LCA 的距离是一样的。且不存在三点公链的情况。

于是设 \(f(u,i)\) 表示在 \(u\) 子树内,距离 \(u\)\(i\) 的点有多少个。

\(g(u,i)\) 表示二元组和他们 LCA 距离相等,还差 \(i\) 距离和他们构成三元组的个数。

于是有 \(ans \leftarrow g(u,j) \times f(v,j-1) + g(v,j) \times f(u,j-1)\)

可以长剖优化。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
typedef long long ll;
const ll N=1e6+5;
const ll inf=1e9;
struct edge{
	ll v,nx;
}e[N<<1];
ll n,ne,f[N],cnt[N],son[N];
ll ans,buf[N],*p=buf,*dp[N],*g[N];
void read(ll u,ll v)
{	e[++ne].v=v;
	e[ne].nx=f[u];
	f[u]=ne;
}
void dfs(ll u,ll ffa)
{	cnt[u]=1;
	for(ll i=f[u];i;i=e[i].nx)
	{	ll v=e[i].v;
		if(v==ffa)continue;
		dfs(v,u);
		cnt[u]=max(cnt[u],cnt[v]+1);
		if(cnt[v]>cnt[son[u]])son[u]=v;
	}
}
void solve(ll u,ll ffa)
{	if(son[u])
	{	dp[son[u]]=dp[u]+1,g[son[u]]=g[u]-1;
		solve(son[u],u);
	}
	dp[u][0]=1,ans+=g[u][0];
	for(ll i=f[u];i;i=e[i].nx)
	{	ll v=e[i].v;
		if(v==ffa||v==son[u])continue;
		dp[v]=p,p+=cnt[v]<<1;g[v]=p,p+=cnt[v]<<1;
		solve(v,u);
		for(ll j=1;j<=cnt[v];j++)ans+=g[u][j]*dp[v][j-1]+g[v][j]*dp[u][j-1];
		for(ll j=1;j<=cnt[v];j++)g[u][j]+=g[v][j+1];
		for(ll j=1;j<=cnt[v];j++)g[u][j]+=dp[u][j]*dp[v][j-1];
		for(ll j=1;j<=cnt[v];j++)dp[u][j]+=dp[v][j-1]; 
	}
}
int main()
{	scanf("%lld",&n);
	for(ll i=1,u,v;i<n;i++)
	{	scanf("%lld%lld",&u,&v);
		read(u,v),read(v,u);
	}
	dfs(1,0);
	dp[1]=p,p+=cnt[1]<<1;g[1]=p,p+=cnt[1]<<1;
	solve(1,0);
	printf("%lld\n",ans);
	return 0;
}

CF809D Hitchhiking in the Baltic States

给出 \(n\) 个区间 \([l_i,r_i]\)\(n\) 个未知数 \(a_1,a_2,\dots,a_n\),现在你要确定这 \(n\) 个数,使得 \(a_i \in [l_i,r_i]\),并且这个序列的最长严格上升子序列尽可能大,求这个最大值。

其中 \(1 \le n \le 3 \times 10^5, 1 \le l_i,r_i \le 10^9\)

\(f(i,j)\) 表示到第 \(i\) 个数时,长度为 \(j\) 的严格 LIS 的最后最小值。于是 \(f(0,0)=0\)

\[f(i,j) \leftarrow \begin{cases} \min(f(i-1,j),l_i) & f(i-1,j-1)<l_i \\ \min(f(i-1,j),f(i-1,j-1)+1) & l_i \le f(i-1,j-1) < r_i \\ f(i-1,j) & f(i-1,j-1) \ge r_i \end{cases} \]

首先 \(i\) 肯定是可是滚掉的。

\[f(j) \leftarrow \begin{cases} \min(f(j),l_i) & f(j-1)<l_i \\ \min(f(j),f(j-1)+1) & l_i \le f(j-1) < r_i \\ f(j) & f(j-1) \ge r_i \end{cases} \]

考虑用平衡树优化,倒着更新。

于是变成了,删除第一个大于等于 \(r\) 的数,区间 \([l,r)\) 平移至 \([l+1,r+1)\),插入第一个大于等于 \(l\) 的数。

然后发现第一类只更新一个数,就是第一个大于等于 \(l\) 的数。于是可以变成删除第一个大于等于 \(l_i\) 再插入 \(l\)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const int N=6e5+5;
const int inf=1e9;
int n,ans;
struct Splay{
	int s[2],p,val,tag;
	void init(int v,int f){
		val=v,p=f;
	}
};
struct tree{
	Splay t[N];
	int rt,tot,top,st[N];
	void build()
	{	rt=1,tot=2;
		t[1].init(-inf,0),t[2].init(inf,rt);
		t[rt].s[1]=2;
	}
	void pushdown(int k)
	{	if(!t[k].tag)return;
		int lc=t[k].s[0],rc=t[k].s[1];
		if(lc)t[lc].val+=t[k].tag,t[lc].tag+=t[k].tag;
		if(rc)t[rc].val+=t[k].tag,t[rc].tag+=t[k].tag;
		t[k].tag=0;
	}
	void rotate(int x)
	{	int y=t[x].p,z=t[y].p;
		int k=(t[y].s[1]==x);
		t[z].s[t[z].s[1]==y]=x;t[x].p=z;
		t[y].s[k]=t[x].s[k^1];t[t[x].s[k^1]].p=y;
		t[x].s[k^1]=y;t[y].p=x;
	}
	void splay(int x,int k)
	{	int tmp=x;top=0;
		st[++top]=tmp;
		while(tmp)st[++top]=tmp=t[tmp].p;
		while(top)pushdown(st[top]),top--;
		while(t[x].p!=k)
		{	int y=t[x].p,z=t[y].p;
			if(z!=k)
			{	if((t[y].s[1]==x)^(t[z].s[1]==y))rotate(x);
				else rotate(y);
			}
			rotate(x);
		}
		if(!k)rt=x;
	}
	void insert(int v)
	{	int u=rt,p=0;ans++;
		while(u)p=u,u=t[u].s[v>t[u].val]; 
		u=++tot;
		if(p)t[p].s[v>t[p].val]=u;
		t[u].init(v,p);
		splay(u,0);
	}
	void del(int p)
	{	splay(p,0);
		int lc=t[p].s[0],rc=t[p].s[1];
		while(t[lc].s[1])lc=t[lc].s[1];
		while(t[rc].s[0])rc=t[rc].s[0];
		splay(lc,0),splay(rc,lc);
		t[rc].s[0]=0;ans--;
	}
	int getpr(int v)
	{	int p=rt,res=0;
		while(p)
		{	pushdown(p);
			if(t[p].val<v)res=p,p=t[p].s[1];
			else p=t[p].s[0];
		}
		splay(rt,0);
		return res;
	}
	int getnx(int v)
	{	int p=rt,res=0;
		while(p)
		{	pushdown(p);
			if(t[p].val>v)res=p,p=t[p].s[0];
			else p=t[p].s[1];
		}
		splay(rt,0);
		return res;
	}
	void move(int l,int r)
	{	int u=getnx(l-1),v=getpr(r);
		if(t[u].val>t[v].val)return;
		if(u==v)t[u].val++;
		else if(t[u].val<t[v].val)
		{	splay(u,0),splay(v,u);
			t[u].val++,t[v].val++;t[t[v].s[0]].val++;
			if(t[v].s[0])t[t[v].s[0]].tag++;
		}
	}
}T;
int main()
{	//freopen("1.in","r",stdin);
	scanf("%d",&n);
	T.build();
	T.insert(0);ans=0;
	for(int i=1,l,r;i<=n;i++)
	{	scanf("%d%d",&l,&r);
		int pos=T.getnx(r-1);
		if(pos&&pos!=1&&pos!=2)T.del(pos);
		T.move(l,r);
		T.insert(l);
	}
	printf("%d\n",ans);
	return 0;
}

[SCOI2014]方伯伯的玉米田

方伯伯在自己的农田边散步,他突然发现田里的一排玉米非常的不美。

这排玉米一共有 \(n\) 株,它们的高度参差不齐。

方伯伯认为单调不下降序列很美,所以他决定先把一些玉米拔高,再把破坏美感的玉米拔除掉,使得剩下的玉米的高度构成一个单调不下降序列。

方伯伯可以选择一个区间,把这个区间的玉米全部拔高 \(1\) 单位高度,他可以进行最多 \(k\) 次这样的操作。拔玉米则可以随意选择一个集合的玉米拔掉。

问能最多剩多少株玉米,来构成一排美丽的玉米。

其中 \(n \le 10^4,k \le 500,a_i \le 5000\)

首先显然选一段后缀执行操作最优。

于是设 \(f(i,j)\) 表示前 \(i\) 个数,于是有 \(f(i,j)=\max\limits_{x<i,y<j,a_i+j \ge a_x+y}\{ f(x,y) \}+1\)

用二维树状数组维护前缀即可。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
const int N=1e4+5,M=500+5;
int n,m,a[N],t[M][N],f[N][M];
int lowbit(int x){return x&-x;}
void modify(int x,int y,int z)
{	for(int i=x;i<=m+1;i+=lowbit(i))
		for(int j=y;j<=5505;j+=lowbit(j))
			t[i][j]=max(t[i][j],z);
}
int query(int x,int y)
{	int res=0;
	for(int i=x;i;i-=lowbit(i))
		for(int j=y;j;j-=lowbit(j))
			res=max(res,t[i][j]);
	return res;
}
int main()
{	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)
		for(int j=m;j>=0;j--)
		{	f[i][j]=query(j+1,a[i]+j)+1;
			modify(j+1,a[i]+j,f[i][j]);
		}
	printf("%d\n",query(m+1,5505));
	return 0;
}

CF983D Arkady and Rectangles

给定 \(n\) 个矩形,一个一个覆盖,问最后能看到几个矩形。

其中 \(n \le 10^5\)

首先扫描线,维护 \(y\) ,如果 \(x\) 能被看到,当且仅当这条线段树上的路径最大值是 \(x\)

但是还要考虑删除的情况,所以每个点开一个 set 存储完全覆盖这个点的颜色,能看到未被看到过的最大和最小颜色。最大的颜色还没统计进去的最大,最小表示不超过最小的就被覆盖。

所以每次更新的时候就直接更新对应区间。pushup 的时候如果最大的被统计过就更新最小。

这题还没写(? 好,补了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#include<vector>
#include<set>
using namespace std;
const int N=2e5+5;
struct node{
	int x,y,xx,yy;
}a[N];
struct qry{
	int l,r,x;
};
int n,ans=1,mx,my,bx[N],by[N];
vector<qry> vc1[N],vc2[N];
bool vis[N];
struct tree{
	int mn[N<<2],mx[N<<2],vl[N<<2],ps[N<<2];
	set<int> st[N<<2];
	void build(int k,int l,int r)
	{	st[k].insert(0);
		mn[k]=mx[k]=vl[k]=ps[k]=0;
		if(l==r)return;
		int mid=(l+r)>>1;
		build(k<<1,l,mid),build(k<<1|1,mid+1,r);
	}
	void pushup(int k)
	{	mn[k]=max(mx[k],min(mn[k<<1],mn[k<<1|1]));
		ps[k]=max(vl[k],max(ps[k<<1],ps[k<<1|1]));
		if(ps[k]<mn[k])ps[k]=0;
	}
	void modify(int k,int l,int r,int x)
	{	x=max(x,mx[k]);
		if(!ps[k]||ps[k]<x)return;
		while(vl[k]&&vl[k]>=max(x,mn[k]))
		{	vis[vl[k]]=1;
			while(vis[vl[k]])vl[k]=(*(--st[k].find(vl[k])));
		}
		if(l==r){mn[k]=mx[k],ps[k]=(vl[k]<mn[k])?(0):(vl[k]);return;}
		int mid=(l+r)>>1;
		modify(k<<1,l,mid,x),modify(k<<1|1,mid+1,r,x);
		pushup(k);
	}
	void modify1(int k,int l,int r,int x,int y,int z,int w)
	{	w=max(w,mx[k]);
		if(x<=l&&r<=y)
		{	if(z>=max(w,mn[k]))vis[z]=1;
			else vl[k]=max(vl[k],z);
			mx[k]=max(mx[k],z),st[k].insert(z);
			if(l==r)mn[k]=mx[k],ps[k]=(vl[k]<mn[k])?(0):(vl[k]);
			else pushup(k);
			return;
		}
		int mid=(l+r)>>1;
		if(x<=mid)modify1(k<<1,l,mid,x,y,z,w);
		if(y>mid)modify1(k<<1|1,mid+1,r,x,y,z,w);
		pushup(k);
	}
	void modify2(int k,int l,int r,int x,int y,int z,int w)
	{	w=max(w,mx[k]);
		if(x<=l&&r<=y)
		{	while(vl[k]==z||vis[vl[k]])vl[k]=(*(--st[k].find(vl[k])));
			st[k].erase(z),mx[k]=(*(--st[k].end()));
			if(l==r)mn[k]=mx[k],ps[k]=(vl[k]<mn[k])?(0):(vl[k]);
			else pushup(k);
			return;
		}
		int mid=(l+r)>>1;
		if(x<=mid)modify2(k<<1,l,mid,x,y,z,w);
		if(y>mid)modify2(k<<1|1,mid+1,r,x,y,z,w);
		pushup(k);
	}
}T;
int main()
{	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{	scanf("%d%d%d%d",&a[i].x,&a[i].y,&a[i].xx,&a[i].yy);
		bx[++mx]=a[i].x,bx[++mx]=a[i].xx;
		by[++my]=a[i].y,by[++my]=a[i].yy;
		a[i].x++,a[i].y++;
	}
	sort(bx+1,bx+1+mx),sort(by+1,by+1+my);
	mx=unique(bx+1,bx+1+mx)-bx-1,my=unique(by+1,by+1+my)-by-1;
	for(int i=n;i>=1;i--)
	{	a[i].x=lower_bound(bx+1,bx+1+mx,a[i].x)-bx;
		a[i].xx=lower_bound(bx+1,bx+1+mx,a[i].xx)-bx;
		a[i].y=lower_bound(by+1,by+1+my,a[i].y)-by;
		a[i].yy=lower_bound(by+1,by+1+my,a[i].yy)-by;
		vc1[a[i].x].push_back((qry){a[i].y,a[i].yy,i});
		vc2[a[i].xx].push_back((qry){a[i].y,a[i].yy,i});
	}
	T.build(1,1,my);
	for(int i=1;i<=mx;i++)
	{	for(int j=0;j<(int)vc1[i].size();j++)
		{	int l=vc1[i][j].l,r=vc1[i][j].r,x=vc1[i][j].x;
			T.modify1(1,1,my,l,r,x,0);
		}
		T.modify(1,1,my,0);
		for(int j=0;j<(int)vc2[i].size();j++)
		{	int l=vc2[i][j].l,r=vc2[i][j].r,x=vc2[i][j].x;
			T.modify2(1,1,my,l,r,x,0);
		}
	}
	for(int i=1;i<=n;i++)
		if(vis[i])ans++;
	printf("%d\n",ans);//
	return 0;
}

鸽了亿些 LCT 和 KD-Tree 的题。


\[\text{by Rainy7} \]

posted @ 2022-02-28 10:39  Rainy7  阅读(166)  评论(0编辑  收藏  举报