暑假集训CSP提高模拟26

暑假集训CSP提高模拟26

\(T1'\) 前缀 \(0pts\)

  • 原题: [ABC366D] Cuboid Sum Query
  • 三维前缀和板子。
    • 由容斥原理,容易有 \(s_{i,j,k}=s_{i,j,k-1}+s_{i,j-1,k}+s_{i-1,j,k}-s_{i-1,j-1,k}-s_{i-1,j,k-1}-s_{i,j-1,k-1}+s_{i-1,j-1,k-1}+a_{i,j,k}\)

    • 类似地,询问时 \(s_{r_{x},r_{y},r_{z}}-s_{r_{x},r_{y},l_{z}-1}-s_{r_{x},l_{y}-1,r_{z}}-s_{l_{x}-1,r_{y},r_{z}}+s_{l_{x}-1,l_{y}-1,r_{z}}+s_{l_{x}-1,r_{y},l_{z}-1}+s_{r_{x},l_{y}-1,l_{z}-1}-s_{l_{x}-1,l_{y}-1,l_{z}-1}\) 即为所求。

      点击查看代码
      int a[110][110][110],s[110][110][110];
      int main()
      {
      	int n,m,lx,ly,lz,rx,ry,rz,i,j,k;
      	cin>>n;
      	for(i=1;i<=n;i++)
      	{
      		for(j=1;j<=n;j++)
      		{
      			for(k=1;k<=n;k++)
      			{
      				cin>>a[i][j][k];
      				s[i][j][k]=s[i][j][k-1]+s[i][j-1][k]+s[i-1][j][k]-s[i-1][j-1][k]-s[i-1][j][k-1]-s[i][j-1][k-1]+s[i-1][j-1][k-1]+a[i][j][k];
      			}
      		}
      	}
      	cin>>m;
      	for(i=1;i<=m;i++)
      	{
      		cin>>lx>>rx>>ly>>ry>>lz>>rz;
      		cout<<s[rx][ry][rz]-s[rx][ry][lz-1]-s[rx][ly-1][rz]-s[lx-1][ry][rz]+s[lx-1][ly-1][rz]+s[lx-1][ry][lz-1]+s[rx][ly-1][lz-1]-s[lx-1][ly-1][lz-1]<<endl;
      	}
      	return 0;
      }
      
  • 枚举一维,另外的二维前缀和维护。

\(T2'\) P270.树 \(100pts\)

\(T3'\) P271.背包 \(100pts\)

\(T4'\) P272.序列 \(0pts\)

  • 第一问在第二问的基础上删除了 \(\gcd(a_{1},a_{2},a_{3}, \dots ,a_{n})=1\) 的限制。

  • 第二问: CF1559E Mocha and Stars

  • 看到 \(\gcd\) 考虑莫反。

  • 推下式子,有 \(\begin{aligned} &\sum\limits_{a_{1}=l_{1}}^{r_{1}}\sum\limits_{a_{2}=l_{2}}^{r_{2}} \dots \sum\limits_{a_{n}=l_{n}}^{r_{n}}[\sum\limits_{i=1}^{n}a_{i} \le m \land \gcd(a_{1},a_{2},a_{3}, \dots ,a_{n})=1] \\ &=\sum\limits_{d=1}^{m}\mu(d)\sum\limits_{a_{1}=l_{1}}^{r_{1}}[d|a_{1}]\sum\limits_{a_{2}=l_{2}}^{r_{2}}[d|a_{2}] \dots \sum\limits_{a_{n}=l_{n}}^{r_{n}}[d|a_{n}] \times [\sum\limits_{i=1}^{n}a_{i} \le m] \\ &=\sum\limits_{d=1}^{m}\mu(d)\sum\limits_{a_{1}=\left\lceil \frac{l_{1}}{d} \right\rceil}^{\left\lfloor \frac{r_{1}}{d} \right\rfloor}\sum\limits_{a_{2}=\left\lceil \frac{l_{2}}{d} \right\rceil}^{\left\lfloor \frac{r_{2}}{d} \right\rfloor}\dots \sum\limits_{a_{n}=\left\lceil \frac{l_{n}}{d} \right\rceil}^{\left\lfloor \frac{r_{n}}{d} \right\rfloor}[\sum\limits_{i=1}^{n}a_{i} \le \left\lfloor \frac{m}{d} \right\rfloor] \end{aligned}\)

  • 提出后半部分,设 \(f_{i,j}\) 表示前 \(i\) 个数中总和为 \(j\) 的方案数,状态转移方程为 \(f_{i,j}=\sum\limits_{k=l_{i}}^{\min(j,r_{i})}f_{i-1,j-k}\) 。前缀和优化即可。

  • 因为有调和级数所以时间复杂度是正确的。

    点击查看代码
    const ll p=998244353;
    ll f[2][100010],s[100010],l[100010],r[100010],nl[100010],nr[100010],prime[100010],vis[100010],miu[100010],len=0;
    void isprime(ll n)
    {
    	memset(vis,0,sizeof(vis));
    	miu[1]=1;
    	for(ll i=2;i<=n;i++)
    	{
    		if(vis[i]==0)
    		{
    			len++;
    			prime[len]=i;
    			miu[i]=-1;
    		}
    		for(ll j=1;j<=len&&i*prime[j]<=n;j++)
    		{
    			vis[i*prime[j]]=1;
    			if(i%prime[j]==0)
    			{
    				miu[i*prime[j]]=0;
    				break;
    			}
    			else
    			{
    				miu[i*prime[j]]=-miu[i];
    			}
    		}
    	}
    }
    ll ask(ll n,ll m)
    {
    	f[0][0]=1;
    	for(ll i=1;i<=m;i++)
    	{
    		f[0][i]=0;
    	}
    	for(ll i=1;i<=n;i++)
    	{
    		for(ll j=0;j<=m;j++)
    		{
    			s[j]=((j-1>=0?s[j-1]:0)+f[(i-1)&1][j])%p;
    			f[i&1][j]=0;
    		}
    		for(ll j=nl[i];j<=m;j++)
    		{
    			f[i&1][j]=(s[j-nl[i]]-((j-nr[i]-1>=0)?s[j-nr[i]-1]:0)+p)%p;
    		}
    	}
    	ll ans=0;
    	for(ll i=0;i<=m;i++)
    	{
    		ans=(ans+f[n&1][i])%p;
    	}
    	return ans;	
    }
    int main()
    {
    	ll n,m,ans=0,i,j;
    	scanf("%lld%lld",&n,&m);
    	isprime(m);
    	for(i=1;i<=n;i++)
    	{
    		scanf("%lld%lld",&l[i],&r[i]);
    	}
    	for(i=1;i<=m;i++)
    	{
    		if(miu[i]!=0)
    		{
    			for(j=1;j<=n;j++)
    			{
    				nl[j]=ceil(1.0*l[j]/i);
    				nr[j]=r[j]/i;
    			}
    			ans=(ans+miu[i]*ask(n,m/i)+p)%p;
    		}
    	}
    	printf("%lld\n",ans);
    	return 0;
    }
    

\(T1\) P273. 博弈 \(0pts\)

  • 原题: 2022牛客OI赛前集训营-提高组(第四场) A 博弈

  • \(cnt_{i}\) 表示路径 \(u \to v\) 的路径上 \(i\) 的出现次数。

  • CF1990A Submission Bait ,不难发现对于点对 \((u,v)\) Crying 必胜当且仅当 \(\exists w \in [0,10^{9}],cnt_{i} \bmod 2=1\)

  • 部分分

    • \(30pts\) :枚举所有点对判断是否有解,每次钦定一个作为起点的单次询问时间复杂度为 \(O(n^{2})\)

      点击查看代码
      struct node
      {
      	ll nxt,to,w;
      }e[1000010];
      ll head[1000010],u[1000010],v[1000010],w[1000010],b[1000010],sum[1000010],num[2],cnt=0,ans;
      void add(ll u,ll v,ll w)
      {
      	cnt++;
      	e[cnt].nxt=head[u];
      	e[cnt].to=v;
      	e[cnt].w=w;
      	head[u]=cnt;
      }
      void dfs(ll x,ll fa,ll rt,ll w)
      {
      	if(x!=rt)
      	{
      		num[sum[w]%2]--;
      		sum[w]++;
      		num[sum[w]%2]++;
      		ans+=(num[1]>=1);
      	}
      	for(ll i=head[x];i!=0;i=e[i].nxt)
      	{
      		if(e[i].to!=fa)	
      		{
      			dfs(e[i].to,x,rt,e[i].w);
      		}
      	}
      	if(x!=rt)
      	{
      		num[sum[w]%2]--;
      		sum[w]--;
      		num[sum[w]%2]++;
      	}
      }
      int main()
      {
      	int t,n,i,j;
      	cin>>t;
      	for(j=1;j<=t;j++)
      	{
      		cin>>n;
      		ans=cnt=0;
      		memset(e,0,sizeof(e));
      		memset(head,0,sizeof(head));
      		for(i=1;i<=n-1;i++)
      		{
      			cin>>u[i]>>v[i]>>w[i];
      			b[i]=w[i];
      		}
      		sort(b+1,b+n);
      		b[0]=unique(b+1,b+n)-(b+1);
      		for(i=1;i<=n-1;i++)
      		{
      			w[i]=lower_bound(b+1,b+n,w[i])-b;
      			add(u[i],v[i],w[i]);
      			add(v[i],u[i],w[i]);
      		}
      		for(i=1;i<=n;i++)
      		{
      			dfs(i,0,i,0);
      		}
      		cout<<ans/2<<endl;
      	}
      	return 0;
      }
      
  • 正解

    • 发现和哈希在本题中无法合适地处理奇偶性,考虑异或哈希。

    • 具体地,给边权随机赋一个 \([0,2^{64})\) 范围内的值 \(H_{w}\) ,一条路径上边权的集合的哈希值就是其中元素的 \(H\) 值的异或和。

      • rand
        • rand 返回一个 [0,RAND_MAX] 的随机整数。在 \(Linux\)RAND_MAX 等于 \(2^{31}-1\) ,达到了 unsigned int 类型的取值范围;但 WindowsRAND_MAX 等于 \(2^{15}-1\) ,仅达到了 short 的上界。
        • 需要调用 #include<random>
      • mt19937
        • mt19937 作用和随机数范围同 rand() ,均为 unsigned int 类型的取值范围,但随机数质量和速度均比 rand() 优。
        • 需要调用 #include<random>
          • mt19937_64 随机数范围扩大到了 unsigned long long 类型的取值范围。
        • 使用 mt19937 时需先定义一个随机数生成器,例如 mt19937 rng(seed) ,其中随机种子 seed 若不填则为默认随机种子。需要生成随机数时直接调用 rng() 即可。
      • random_device
        • 梅森旋转器 random_device 是一个基于硬件的均匀分布随机数生成器,在 熵池耗尽前 效率较高,耗尽后性能急剧下降,常用作 mt19937 等伪随机数生成器的种子来源。例如 mt19937 rng(random_device{}());
        • 需要调用 #include<random>
    • 另外一种异或哈希的方法。

    • 基于异或的性质,树上差分比较容易,维护树上前缀和后桶维护个数。

    点击查看代码
    struct node
    {
    	ull nxt,to,w;
    }e[1000010];
    ull head[1000010],sum[1000010],cnt=0,ans;
    mt19937_64 rng(random_device{}());
    map<ull,ull>f,g;
    map<ull,ull>::iterator it;
    void add(ull u,ull v,ull w)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	e[cnt].w=w;
    	head[u]=cnt;
    }
    void dfs(ull x,ull fa,ull w)
    {
    	sum[x]=sum[fa]^w;
    	ans-=g[sum[x]];
    	g[sum[x]]++;
    	for(ull i=head[x];i!=0;i=e[i].nxt)
    	{
    		if(e[i].to!=fa)
    		{
    			dfs(e[i].to,x,e[i].w);
    		}
    	}
    }
    int main()
    {
    	ull t,n,u,v,w,i,j;
    	cin>>t;
    	for(j=1;j<=t;j++)
    	{
    		cin>>n;
    		ans=n*(n-1)/2;
    		cnt=0;
    		memset(e,0,sizeof(e));
    		memset(head,0,sizeof(head));
    		g.clear();
    		for(i=1;i<=n-1;i++)
    		{
    			cin>>u>>v>>w;
    			if(f.find(w)==f.end())
    			{
    				f[w]=rng();
    			}
    			add(u,v,f[w]);
    			add(v,u,f[w]);
    		}
    		dfs(1,0,0);
    		cout<<ans<<endl;
    	}
    	return 0;
    }
    

\(T2\) P274. 跳跃 \(20pts\)

  • 原题: 2022牛客OI赛前集训营-提高组(第四场) B 跳跃

  • 部分分

    • \(20pts\) :爆搜。

      点击查看代码
      ll a[1010],sum[1010],ans;
      void dfs(ll pos,ll x,ll num,ll n,ll k)
      {
      	ans=max(ans,num);
      	if(pos==k+1)
      	{
      		return;
      	}
      	else
      	{
      		if(pos&1)
      		{
      			for(ll i=x;i<=n;i++)
      			{
      				if(num+sum[i]-sum[x-1]>=0)
      				{
      					dfs(pos+1,i,num+sum[i]-sum[x-1],n,k);
      				}
      			}
      		}
      		else
      		{
      			for(ll i=1;i<=x;i++)
      			{
      				if(num+sum[x]-sum[i-1]>=0)
      				{
      					dfs(pos+1,i,num+sum[x]-sum[i-1],n,k);
      				}
      			}
      		}
      	}
      }
      int main()
      {
      	ll t,n,k,i,j;
      	scanf("%lld",&t);
      	for(j=1;j<=t;j++)
      	{
      		scanf("%lld%lld",&n,&k);
      		ans=0;
      		for(i=1;i<=n;i++)
      		{
      			scanf("%lld",&a[i]);
      			sum[i]=sum[i-1]+a[i];
      		}
      		dfs(1,1,0,n,k);
      		printf("%lld\n",ans);
      	}
      	return 0;
      }
      
    • \(f_{i,j}\) 表示从 \(1\) 跳到 \(i\) 一共跳了 \(j\) 步的最大得分,状态转移方程为 \(f_{i,j}=\begin{cases} \max\limits_{k=1}^{i} \{ f_{k,j-1}+\sum\limits_{h=k}^{i}a_{h} \} & j \bmod 2=0 \\ \max\limits_{k=i}^{n} \{ f_{k,j-1}+\sum\limits_{h=i}^{k}a_{h} \} & j \bmod 2=1 \end{cases}\) ,维护前/后缀 \(\max\) 后时间复杂度为 \(O(nk)\)

  • 正解

    • 不难想到,我们会在一些连续段反复横跳,使得能跳到下一个更优的连续段继续反复横跳。
    • 处理出 \(suf_{i}=\max\limits_{j=1}^{i}\{ \sum\limits_{k=j}^{i}a_{k} \}\)
    • \(f_{i}\) 表示从 \(1\) 跳到 \(i\) 后至多还能跳几次, \(g_{i}\) 表示在前者取到最优时从 \(1\) 跳到 \(i\) 时的最大得分。
    • 转移时枚举上一步 \(j\)\(suf_{j}\) 可以方便地计算出跳到 \(i\) 的最小次数。
    • 最后统计答案时枚举所有右端点进行左右横跳即可。
    点击查看代码
    ll f[1010],g[1000],a[1010],sum[1010],suf[1010];
    int main()
    {
    	ll t,n,k,ans,minn,d,cnt,num,i,j,h;
    	cin>>t;
    	for(h=1;h<=t;h++)
    	{
    		cin>>n>>k;
    		ans=minn=0;
    		for(i=1;i<=n;i++)
    		{
    			cin>>a[i];
    			sum[i]=sum[i-1]+a[i];
    			suf[i]=sum[i]-minn;
    			minn=min(minn,sum[i]);
    			if(sum[i]>=0)
    			{
    				f[i]=k-1;
    				g[i]=sum[i];
    			}
    			else
    			{
    				f[i]=-1;
    				g[i]=-0x7f7f7f7f;
    			}
    		}
    		for(i=1;i<=n;i++)
    		{
    			for(j=i+1;j<=n;j++)
    			{
    				if(suf[j]>=suf[i])
    				{
    					d=sum[j]-sum[i];
    					if(g[i]+d<0)
    					{
    						if(suf[i]>0)
    						{
    							cnt=ceil((0-(g[i]+d))/(2.0*suf[i]));
    							num=g[i]+d+2*cnt*suf[i];
    							if(f[j]<f[i]-2*cnt)
    							{
    								f[j]=f[i]-2*cnt;
    								g[j]=num;
    							}
    							else
    							{	
    								if(f[j]==f[i]-2*cnt)
    								{
    									g[j]=max(g[j],num);
    								}
    							}
    						}
    					}
    					else
    					{
    						if(f[j]<f[i]-(i==1))
    						{
    							f[j]=f[i]-(i==1);
    							g[j]=g[i]+d;
    						}
    						else
    						{
    							if(f[j]==f[i]-(i==1))
    							{
    								g[j]=max(g[j],g[i]+d);
    							}
    						}
    					}
    				}
    			}			
    		}
    		for(i=1;i<=n;i++)
    		{
    			ans=max(ans,f[i]*suf[i]+g[i]);
    		}
    		cout<<ans<<endl;
    	}
    	return 0;
    }
    

\(T3\) P275. 大陆 \(0pts\)

  • 原题: 2022牛客OI赛前集训营-提高组(第四场) C 大陆 | luogu P2325 [SCOI2005] 王室联邦

  • 貌似是树分块的前置知识。

  • 考虑对整棵子树进行 \(DFS\) ,处理每个节点时先将部分子节点分块,未被分块的子节点回溯到上一层。

  • 具体地,枚举 \(x\) 的所有子节点 \(y\) ,递归处理完子树后,将未被分块的子节点加入待选集合 \(S\) 中,途中一旦 \(|S| \ge b\) 就将 \(S\) 作为一个新的块(块大小至多为 \(2b-2\) )并以 \(x\) 作为省会,并清空 \(S\) 。接着加入 \(x\) ,此时 \(|S|\) 至多为 \(b-1+1=b\)

  • 最后把集合 \(S\) 中剩余的节点加入最后一个块,块大小至多为 \(2b-2+b+1=3b-1\) ,符合题意。

  • 特判只有一个节点的情况。

    点击查看代码
    struct node
    {
    	int nxt,to;
    }e[20010];
    int head[20010],pos[20010],rt[20010],cnt=0,ksum=0;
    stack<int>s;
    void add(int u,int v)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	head[u]=cnt;
    }
    void dfs(int x,int fa,int b)
    {
    	int tmp=s.size();
    	for(int i=head[x];i!=0;i=e[i].nxt)
    	{
    		if(e[i].to!=fa)
    		{
    			dfs(e[i].to,x,b);
    			if(s.size()-tmp>=b)
    			{
    				ksum++;
    				rt[ksum]=x;
    				while(s.size()>tmp)
    				{
    					pos[s.top()]=ksum;
    					s.pop();
    				}
    			}
    		}
    	}
    	s.push(x);
    }
    int main()
    {
    	int n,b,u,v,i;
    	cin>>n>>b;
    	for(i=1;i<=n-1;i++)
    	{
    		cin>>u>>v;
    		add(u,v);
    		add(v,u);
    	}
    	dfs(1,0,b);
    	if(ksum==0)
    	{
    		ksum++;
    		rt[ksum]=1;
    	}
    	while(s.empty()==0)
    	{
    		pos[s.top()]=ksum;
    		s.pop();
    	}
    	cout<<ksum<<endl;
    	for(i=1;i<=n;i++)
    	{
    		cout<<pos[i]<<" ";
    	}
    	cout<<endl;
    	for(i=1;i<=ksum;i++)
    	{
    		cout<<rt[i]<<" ";
    	}
    	cout<<endl;
    	return 0;
    }
    

\(T4\) P276. 排列 \(40pts\)

  • 原题: 2022牛客OI赛前集训营-提高组(第四场) D 排列 | Baekjoon 17961.수열과 쿼리 35

  • 部分分

    • \(40pts\) :暴力进行循环右移,权值树状数组重构整个序列,时间复杂度为 \(O(qn \log n)\)

      点击查看代码
      int a[120010],b[120010];
      struct BIT
      {
      	int c[120010];
      	int lowbit(int x)
      	{
      		return (x&(-x));
      	}
      	void init(int n)
      	{
      		for(int i=1;i<=n;i++)
      		{
      			c[i]=0;
      		}
      	}
      	void add(int n,int x,int val)
      	{
      		for(int i=x;i<=n;i+=lowbit(i))
      		{
      			c[i]+=val;
      		}
      	}
      	int getsum(int x)
      	{
      		int ans=0;
      		for(int i=x;i>=1;i-=lowbit(i))
      		{
      			ans+=c[i];
      		}
      		return ans;
      	}
      }le,ri;
      bool ask(int l,int r,int k,int n)
      {
      	for(int i=l;i<=r;i++)
      	{
      		b[i]=a[i];
      	}
      	for(int i=l,j=r-k+1;i<=l+k-1;i++,j++)
      	{
      		a[i]=b[j];
      	}
      	for(int i=l+k,j=l;i<=r;i++,j++)
      	{
      		a[i]=b[j];
      	}
      	le.init(n);
      	ri.init(n);
      	for(int i=1;i<=n;i++)
      	{
      		ri.add(n,a[i],1);
      	}
      	for(int i=1;i<=n;i++)
      	{
      		ri.add(n,a[i],-1);
      		if(le.getsum(a[i]-1)>=1&&ri.getsum(n)-ri.getsum(a[i])>=1)
      		{
      			return true;
      		}
      		le.add(n,a[i],1);
      	}
      	return false;
      }
      int main()
      {
      	int n,m,l,r,k,i;
      	cin>>n;
      	for(i=1;i<=n;i++)
      	{
      		cin>>a[i];
      	}
      	cin>>m;
      	for(i=1;i<=m;i++)
      	{
      		cin>>l>>r>>k;
      		if(ask(l,r,k,n)==true)
      		{
      			cout<<"YES"<<endl;
      		}
      		else
      		{
      			cout<<"NO"<<endl;
      		}
      	}
      	return 0;
      }
      
  • 正解

    • 循环右移使用 \(FHQ-Treap\) 是容易维护的。
    • 先考虑当询问二元上升子序列时怎么做,不难想到维护 \(tag\) 标记表示子树内是否存在二元上升子序列和子树内的最大值 \(maxv\) 、最小值 \(minv\)\(tag\) 的合并直接 pushup 时即可。
    • 扩展到三元上升子序列后可能会出现某棵子树对答案贡献了两个元素作为三元上升子序列的一部分,考虑维护子树内最大的元素 \(maxt\) 使得存在以 \(maxt\) 开头的二元上升子序列和最小的元素 \(mint\) 使得存在以 \(mint\) 结尾的二元上升子序列。类似的就能很方便地合并得到 \(ans\) 表示子树内是否存在三元上升子序列。
    • 难点在于 \(maxt,mint\) 一个值在左子树、另一个值在右子树的合并。
    • \(maxt\) 为例,首先有 \(maxt_{x}=\max(maxt_{ls},maxt_{rs})\) 。同时可能存在右子树取 \(maxt_{rs}\) ,左子树取 \(<maxt_{rs}\) 的最大元素(即取前驱)。
    • 仅考虑整棵子树内不存在三元上升子序列时(因为平衡树维护下标时不能很容易地维护权值)再去更新 \(maxt\) ,得到左子树 \(<maxt_{rs}\) 的权值单调递减,直接去左子树上找即可。
    • 时间复杂度为 \(O(q \log^{2}n)\)
    点击查看代码
    int a[120010];
    struct BST
    {	
    	const int inf=0x3f3f3f3f;
    	int rt_sum,root;
    	struct FHQ_Treap
    	{
    		int son[2],val,rnd,cnt,siz,maxv,minv,maxt,mint,tag,ans;
    	}tree[120010];
    	#define lson(rt) (tree[rt].son[0])
    	#define rson(rt) (tree[rt].son[1])
    	BST()
    	{
    		rt_sum=root=0;
    		tree[0].minv=tree[0].mint=inf;
    		tree[0].maxv=tree[0].maxt=-inf;
    		srand(time(0));
    	}
    	int find_pre(int rt,int k)
    	{
    		if(tree[rt].minv>=k)
    		{
    			return -inf;
    		}
    		if(tree[lson(rt)].minv<k)
    		{
    			return find_pre(lson(rt),k);
    		}
    		else
    		{
    			if((tree[rt].val<k))
    			{
    				return tree[rt].val;
    			}
    			else
    			{
    				return find_pre(rson(rt),k);
    			}
    		}
    	}
    	int find_nxt(int rt,int k)
    	{
    		if(tree[rt].maxv<=k)
    		{
    			return inf;
    		}
    		if(tree[lson(rt)].maxv>k)
    		{
    			return find_nxt(lson(rt),k);
    		}
    		else
    		{
    			if((tree[rt].val>k))
    			{
    				return tree[rt].val;
    			}
    			else
    			{
    				return find_nxt(rson(rt),k);
    			}
    		}
    	}
    	void pushup(int rt)
    	{
    		tree[rt].siz=tree[lson(rt)].siz+tree[rson(rt)].siz+tree[rt].cnt;
    		tree[rt].tag=tree[lson(rt)].tag|tree[rson(rt)].tag|(tree[lson(rt)].minv<tree[rson(rt)].maxv);
    		tree[rt].tag|=(tree[lson(rt)].minv<tree[rt].val)|(tree[rt].val<tree[rson(rt)].maxv);
    		tree[rt].maxv=max(tree[rt].val,max(tree[lson(rt)].maxv,tree[rson(rt)].maxv));
    		tree[rt].minv=min(tree[rt].val,min(tree[lson(rt)].minv,tree[rson(rt)].minv));
    		tree[rt].ans=tree[lson(rt)].ans|tree[rson(rt)].ans|(tree[lson(rt)].mint<tree[rson(rt)].maxv)|(tree[lson(rt)].minv<tree[rson(rt)].maxt);
    		tree[rt].ans|=(tree[lson(rt)].minv<tree[rt].val&&tree[rt].val<tree[rson(rt)].maxv);
    		tree[rt].ans|=(tree[lson(rt)].mint<tree[rt].val)|(tree[rt].val<tree[rson(rt)].maxt);
    		tree[rt].maxt=max(tree[lson(rt)].maxt,tree[rson(rt)].maxt);
    		tree[rt].mint=min(tree[lson(rt)].mint,tree[rson(rt)].mint);
    		if(tree[rt].ans==0)
    		{
    			if(tree[rt].val<tree[rson(rt)].maxv)
    			{
    				tree[rt].maxt=max(tree[rt].maxt,tree[rt].val);
    			}
    			tree[rt].maxt=max(tree[rt].maxt,find_pre(lson(rt),tree[rt].val));
    			tree[rt].maxt=max(tree[rt].maxt,find_pre(lson(rt),tree[rson(rt)].maxv));
    			if(tree[lson(rt)].mint<tree[rt].val)
    			{
    				tree[rt].mint=min(tree[rt].mint,tree[rt].val);
    			}
    			tree[rt].mint=min(tree[rt].mint,find_nxt(rson(rt),tree[rt].val));
    			tree[rt].mint=min(tree[rt].mint,find_nxt(rson(rt),tree[lson(rt)].minv));
    		}
    	}
    	int build(int val)
    	{
    		rt_sum++;
    		lson(rt_sum)=rson(rt_sum)=0;
    		tree[rt_sum].val=tree[rt_sum].maxv=tree[rt_sum].minv=val;
    		tree[rt_sum].rnd=rand();
    		tree[rt_sum].cnt=tree[rt_sum].siz=1;
    		tree[rt_sum].maxt=-inf;
    		tree[rt_sum].mint=inf;
    		return rt_sum;
    	}
    	void split(int rt,int k,int &ls,int &rs)
    	{
    		if(rt==0)
    		{
    			ls=rs=0;
    			return;
    		}
    		if(tree[lson(rt)].siz+tree[rt].cnt<=k)
    		{
    			ls=rt;
    			split(rson(ls),k-tree[lson(rt)].siz-tree[rt].cnt,rson(ls),rs);
    		}
    		else
    		{
    			rs=rt;
    			split(lson(rs),k,ls,lson(rs));
    		}
    		pushup(rt);
    	}
    	int merge(int rt1,int rt2)
    	{
    		if(rt1==0||rt2==0)
    		{
    			return rt1+rt2;
    		}
    		if(tree[rt1].rnd<tree[rt2].rnd)
    		{
    			rson(rt1)=merge(rson(rt1),rt2);
    			pushup(rt1);
    			return rt1;
    		}
    		else
    		{
    			lson(rt2)=merge(rt1,lson(rt2));
    			pushup(rt2);
    			return rt2;
    		}
    	}
    	void insert(int pos,int val)
    	{
    		int ls,rs;
    		split(root,pos,ls,rs);
    		root=merge(merge(ls,build(val)),rs);
    	}	
    	int query(int l,int r,int k)
    	{
    		int ls,rs,mid;
    		split(root,r,ls,rs);
    		split(ls,r-k,ls,mid);
    		ls=merge(ls,rs);
    		split(ls,l-1,ls,rs);
    		root=merge(merge(ls,mid),rs);
    		return tree[root].ans;
    	}
    }T;
    int main()
    {
    // #define Isaac
    #ifdef Isaac
    	freopen("in.in","r",stdin);
    	freopen("out.out","w",stdout);
    #endif
    	int n,m,l,r,k,i;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i];
    		T.insert(i,a[i]);
    	}
    	cin>>m;
    	for(i=1;i<=m;i++)
    	{
    		cin>>l>>r>>k;
    		cout<<((T.query(l,r,k)==1)?"YES":"NO")<<endl;
    	}
    	return 0;
    }
    

总结

  • \(T1\)\(VScode\) 终端编译延迟导致显示的是上份代码的信息,以为是这份代码的信息,然后没看出是 \(CE\) ,挂了 \(30pts\)

后记

  • 组题人对 \(T1',T2',T3',T4'\) 的评价。

  • \(T2',T3'\) 我们都打过,所以 \(miaomiao\) 干脆把原来的 \(4\) 道题都换了。原 \(7:00\) 开始的比赛改成了 \(7:10\) 开始。

  • \(T4\) 官方题解纰漏较多。

posted @ 2024-08-21 15:12  hzoi_Shadow  阅读(139)  评论(11编辑  收藏  举报
扩大
缩小