250512 模拟赛总结

void init()
{
	for(int j=1;j<lmt;j++)
		for(int i=1;i<=n-(1<<j)+1;i++)st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
int query(int l,int r)
{
	int k=log2(r-l+1);return max(st[l][k],st[r-(1<<k)+1][k]);
}

分数:\(73+35+12=120\)。绿题都过不了我也是个神人。

T1 几乎写了正解之外的所有 sub 然后花了半天把它们拼起来,终于拼完了,但是都没有想过去打正解,其实思路绕太远了,一直在想怎样维护 dfs 序上的一些连续段,但是实际上因为限制比较紧分讨一些条件简单维护一些信息就可以了。因为有子任务依赖拼 sub 拼到十点多(七点半开始啊啊啊),我可能前边在犯困。

T2 写了个暴力,感觉非常的困难,想到十一点写了一些暴力 + 性质。

T3 依然是暴力 + 性质。好像已经没有时间想更多这个题了。


看似有三道交互题,实际上有 \(0\) 道交互题。


T1 九月的树

原题

发现可以分割为一段相当于所有志愿者在上一次划分到这一次划分中看到的叶子集合是相同的,而且是一些完整的子树。然后考场上想到暴力用 bitset 维护叶子集合然后用线段树维护子树的条件。想复杂了,导致连这么简单一道题都没有过。;;

发现第一个条件相当于在前 \(k\) 天内所有被看到的叶子总数恰好是 \(k\)。第二个条件可以记录所有看到过的叶子可以让哪些叶子真的掉下来(叶子已经出现过而且它的所有儿子已经出现过)。两个情况都满足的时候就可以在这里新分出来一段。

点击查看代码
#include<bits/stdc++.h>
// #include"september.h"
using namespace std;
const int N=1e5+10;
int siz[N];
bool vis[N];
int num1=0,num2=0;
int solve(int n,int m,vector<int>f,vector<vector<int>>s)
{
	int k=0;num1=num2=0;
	memset(siz,0,n*sizeof(int));memset(vis,0,n*sizeof(bool));
	for(int i=1;i<n;i++)++siz[f[i]];
	for(int i=0;i<n-1;i++)
	{
		for(int j=0;j<m;j++)
			if(!vis[s[j][i]])
			{
				vis[s[j][i]]=true;--siz[f[s[j][i]]];
				++num1;
				if(!siz[f[s[j][i]]]&&vis[f[s[j][i]]])++num2;
				if(!siz[s[j][i]])++num2;
			}
		if(num1==i+1&&num2==i+1)++k;
	}
	return k;
}

T2 序列难题

原题

正解思路非常神的一题!

烤肠上写了 \(O(n^2\log n)\) 暴力(对顶堆维护当前中位数)和单峰的性质分(记录一个元素第一次和最后一次出现然后分讨)。

然后看了这篇题解%%%,关于中位数有一个(应该比较)常用的小 trick,对于一个 \(k\),把 \(>k\) 的数看作 \(1\)\(<k\) 的看作 \(0\)。设对于一个 \(k\)\(n1_i\) 表示 \([1,i]\)\(1\) 的个数,\(n0_i,nk_i\) 同理。对于一个区间\([l,r]\)\(k\) 能成为中位数的充要条件是 \(max((n0_r-n1_r+nk_r)-(n0_{l-1}-n1_{l-1}+nk_{l-1}),(n1_r-n0_r+nk_r)-(n1_{l-1}-n0_{l-1}+nk_{l-1}))\le 0\)。设 \(u_i=n0_i-n1_i+nk_i,v_i=n1_i-n0_i+nk_i,p_i=(u_i,v_i)\),以上条件等价于 \(p_{l-1}\)\(p_r\) 的左下方。

发现一个区间内如果没有 \(k\) 的出现,这段区间会形成一条斜率为 \(-1\) 的线段,如果有 \(k\) 将会让后面的点沿一条斜率为 \(1\) 的线段移动到下一条斜率为 \(-1\) 的线段上。那么一个区间中 \(k\) 的出现次数就是从 \(p_{l-1}\)\(p_r\) 经过了多少条斜率为 \(1\) 的线段,也就是两点所在直线间的距离。那么问题转化为最大化两条合法线段的距离。

怎样的两条线段是合法的呢?设线段 \(AB\)\(CD\),不妨设 \(A_x\ge B_x,C_x\ge D_x\),且 \(AB\) 到原点的距离 \(\ge CD\) 到原点的距离。发现这一组线段合法等价于 \(D_y\le A_y\)\(C_x\le B_x\),即 \((C_x,D_y)\)\((B_x,A_y)\) 的左下方。

线段树维护 \(p_{0\sim n}\) 的横坐标(可以通过一些手段由此算出点的纵坐标)。当 \(k\) 变大 \(1\) 的时候,原先的 \(1\rightarrow k,pos_{i}\sim n\) 的横坐标不变,纵坐标 \(-2\)(应该是吧,虽然我没有维护纵坐标);\(k\) 将要变得更大的时候,\(pos_i\sim n\) 的纵坐标不变,横坐标 \(+2\)。横坐标需要区间修改,区间查询最大/最小值,可以用线段树维护,根据一个点的横坐标就可以算出纵坐标。对于每个 \(k\),查询 \(pos_{i-1}\sim pos_{i}-1\) 之间的最大/最小横坐标,就可以得到这条线段的两个端点。

对于线段 \(AB\)(不妨设 \(A_x<B_x\)),需要确定两个点:\((A_x,B_y)\) 被查询,\((B_x,A_y)\) 做查询。每次查询后一类点的左下方所有前一类点的贡献最大值。二维数点问题,用树状数组可以解决。

点击查看代码
#include<bits/stdc++.h>
#include"sequence.h"
#define lowbit(x) ((x)&(-(x)))
#define mid ((lf+rt)>>1)
using namespace std;
const int N=1e6+10;
vector<int>pos[N];
int mn[N*4],mx[N*4],tag[N*4];
inline void psu(int p)
{
	mn[p]=min(mn[p<<1],mn[p<<1|1]);mx[p]=max(mx[p<<1],mx[p<<1|1]);
}
int n;
void build(int p,int lf,int rt)
{
	if(lf==rt)
	{
		mn[p]=mx[p]=n-lf;return;
	}
	build(p<<1,lf,mid);
	build(p<<1|1,mid+1,rt);
	psu(p);
}
inline void psd(int p)
{
	if(tag[p])
	{
		tag[p<<1]+=tag[p];tag[p<<1|1]+=tag[p];
		mn[p<<1]+=tag[p];mn[p<<1|1]+=tag[p];
		mx[p<<1]+=tag[p];mx[p<<1|1]+=tag[p];
		tag[p]=0;
	}
}
int quen(int l,int r,int p,int lf,int rt)
{
	if(l<=lf&&rt<=r)return mn[p];
	psd(p);
	if(r<=mid)return quen(l,r,p<<1,lf,mid);
	if(l>mid)return quen(l,r,p<<1|1,mid+1,rt);
	return min(quen(l,r,p<<1,lf,mid),quen(l,r,p<<1|1,mid+1,rt));
}
int quex(int l,int r,int p,int lf,int rt)
{
	if(l<=lf&&rt<=r)return mx[p];
	psd(p);
	if(r<=mid)return quex(l,r,p<<1,lf,mid);
	if(l>mid)return quex(l,r,p<<1|1,mid+1,rt);
	return max(quex(l,r,p<<1,lf,mid),quex(l,r,p<<1|1,mid+1,rt));
}
void upd(int l,int r,int k,int p,int lf,int rt)
{
	if(l<=lf&&rt<=r)
	{
		mn[p]+=k;mx[p]+=k;tag[p]+=k;return;
	}
	psd(p);
	if(l<=mid)upd(l,r,k,p<<1,lf,mid);
	if(r>mid)upd(l,r,k,p<<1|1,mid+1,rt);
	psu(p);
}
struct node{
	int x,y,v,k;
	friend bool operator<(node a,node b)
	{
		return a.x==b.x?a.y==b.y?a.k<b.k:a.y<b.y:a.x<b.x;
	}
}p[N];
int tr[N*4+50];
inline int que(int x)
{
	int res=0xcfcfcfcf;while(x)res=max(res,tr[x]),x-=lowbit(x);return res;
}
int inf;
inline void add(int x,int k)
{
	while(x<=inf)tr[x]=max(k,tr[x]),x+=lowbit(x);
}
inline void cle(int x)
{
	while(x<=inf)tr[x]=0xcfcfcfcf,x+=lowbit(x);
}
int sequence(int n,vector<int>a)
{
	int ans=1;
	inf=n*2+15;
	for(int i=1;i<=n;i++)pos[i].push_back(0);
	for(int i=0;i<n;i++)pos[a[i]].push_back(i+1);
	for(int i=1;i<=n;i++)pos[i].push_back(n+1);
	build(1,0,n);
	memset(tr,0xcf,sizeof tr);
	for(int i=1;i<=n;i++)
	{
		if(pos[i].size()==2)continue;int cnt=0;
		for(int j=0;j<pos[i].size()-1;j++)
		{
			p[++cnt].x=quen(pos[i][j],pos[i][j+1]-1,1,0,n)+n+5,
			p[++cnt].x=quex(pos[i][j],pos[i][j+1]-1,1,0,n)+n+5;
			int d=p[cnt].x-p[cnt-1].x;
			p[cnt-1].y=-j-(p[cnt].x+j)+n*2+10,
			p[cnt-1].v=j;
			p[cnt-1].k=0;//add
			p[cnt].y=p[cnt-1].y+d;
			p[cnt].v=j;
			p[cnt].k=1;//que
		}
		sort(p+1,p+cnt+1);
		for(int j=1;j<=cnt;j++)
		{
			if(p[j].k)ans=max(ans,que(p[j].y)-p[j].v);
			else add(p[j].y,p[j].v);
		}
		for(int j=1;j<=cnt;j++)cle(p[j].y);
		for(int j=1;j<pos[i].size()-1;j++)upd(pos[i][j],n,2,1,0,n);
	}
	return ans;
}

T3 跳跃计划

原题

考场上没有太多时间去想这道题,写了 \(O(n^3)\) 的暴力。

对于一次询问,首先判断是否有解,即 \([B,C-1]\) 区间内的 \(max\) 是否大于 \([C,D]\) 内最大的高度。

然后做一次倍增,查找 \([A,B]\) 内最大的有解区间(最高树高度小于 \([C,D]\) 内最大高度)。

设当前点左右两边更高的树高度为 \(h\),跳的策略如下:

  • \(h\) 不大于 \([B,C-1]\) 时直接跳最高的树。
  • \(h\) 大于等于 \([C,D]\) 的最大值时,一直往右边跳。
  • 其他情况,如果左边能到的下标在 \([C,D]\),跳一步结束;否则说明需要两步跳到中点。

感觉思路比较的玄学。

点击查看代码
#include<bits/stdc++.h>
// #include"jumps.h"
#define mx(x,y) (A[x]>A[y])?(x):(y)
#define max(x,y) A[que(x,y)]
using namespace std;
const int N=4e5+10;
int A[N],lg2[N],tmp[20][N],l[20][N],r[20][N];
inline int que(int l,int r)
{
	int k=lg2[r-l+1];
	return mx(tmp[k][l],tmp[k][r-(1<<k)+1]);
}
int lmt;
int st[N],top=0;
void init(int n,vector<int>h)
{
	for(int i=2;i<=n;i++)lg2[i]=lg2[i>>1]+1;
	for(int i=1;i<=n;i++)tmp[0][i]=i,A[i]=h[i-1];
	lmt=lg2[n]+1;
	for(int i=1;i<=n;i++)
	{
		while(top&&h[st[top]-1]<A[i])--top;
		l[0][i]=st[top];st[++top]=i;
	}
	top=0;
	for(int i=n;i;i--)
	{
		while(top&&h[st[top]-1]<A[i])--top;
		r[0][i]=st[top];st[++top]=i;
	}
	for(int i=1;i<=n;i++)l[0][i]=mx(l[0][i],r[0][i]);
	for(int i=1;i<=lmt;i++)
		for(int j=0;j<=n;j++)
			r[i][j]=r[i-1][r[i-1][j]],l[i][j]=l[i-1][l[i-1][j]],
			tmp[i][j]=mx(tmp[i-1][j],tmp[i-1][j+(1<<(i-1))]);
}
int minimum_jumps(int a,int b,int c,int d)
{
	++a,++b,++c,++d;int p=b,x=max(b,c-1),y=max(c,d),res=0,t=b;
	if(x>y)return -1;
	for(int i=lmt;i>=0;i--)
	{
		t=p-(1<<i);if(t>=a&&A[tmp[i][t]]<y)p=t;
	}
	a=que(p,b);
	for(int i=lmt;i>=0;i--)if(A[p=l[i][a]]<x&&p)a=p,res+=(1<<i);
	if(r[0][a]>=c)return res+1;
	if(A[l[0][a]]<y)return res+2;
	for(int i=lmt;i>=0;i--)if((p=r[i][a])<c&&p)a=p,res+=(1<<i);
	return res+1;
}
posted @ 2025-05-12 20:45  baiguifan  阅读(31)  评论(2)    收藏  举报