题解:[BalticOI 2024] Wall

题目传送门

题意分析

对于 \(2^n\) 种序列 \(h\),都有 \(h_i=a_i\) 或 \(h_i=b_i\)。先考虑对于确定的 \(h\),如何计算答案。

显然可以对 \(a,b\) 离散化,记离散化后值域为 \([1,m]\),\(m\leq 2n\)。下文中不再区分离散化前/后的值。

\(h_i\) 的水位线 \(H_i\) 需要满足 \(\exist l\leq i\leq r,H_i\leq\min(h_l,h_r)\) 且 \(H_i\) 最大,即 \(H_i=\min(\textit{pre}_i,\textit{suf}_i)\)。\(\textit{pre}_i,\textit{suf}_i\) 分别为前缀/后缀最大值。

于是有序列 \(h\) 的贡献为:

\[\sum_{i=1}^n(\min(\textit{pre}_i,\textit{suf}_i)-h_i) \]

前/后缀 \(\max\) 取 \(\min\),不好计算,考虑转换一下。经典结论 \(\min(a,b)=a+b-\max(a,b)\),而 \(\max(\textit{pre}_i,\textit{suf}_i)\) 就是全局 \(\max\),记为 \(f\):

\[\begin{aligned} \sum_{i=1}^n(\min(\textit{pre}_i,\textit{suf}_i)-h_i)&=\sum_{i=1}^n(\textit{pre}_i+\textit{suf}_i-h_i-f)\\ &=\sum_{i=1}^n\textit{pre}_i+\sum_{i=1}^n\textit{suf}_i-\sum_{i=1}^nh_i-nf \end{aligned} \]

\(\mathcal O(2^n)\) 枚举肯定是不行的,考虑全局统计贡献。

先考虑统计 \(\displaystyle\sum_{i=1}^nh_i\),这是好统计的。因为 \(h_i=a_i\) 或 \(h_i=b_i\) 都只有 \(2^{n-1}\) 种情况,这一部分的总贡献即:

\[-\sum_{i=1}^n2^{n-1}(a_i+b_i) \]

再考虑 \(nf\) 怎么做。考虑 \(f\) 只有 \(m\) 种取值,枚举 \(f\) 的取值,随后计算最大值为 \(f\) 的方案数。记 \(k(x)\) 表示最大值小于等于 \(x\) 的方案数量。

考虑 \(a_i,b_i\) 对于方案的贡献:

  • 如果 \(\max(a_i,b_i)\leq x\),则方案数为 \(2\)。
  • 如果 \(\min(a_i,b_i)\leq x<\max(a_i,b_i)\),则方案数为 \(1\)。
  • 如果 \(x<\min(a_i,b_i)\),则方案数为 \(0\)。

总方案数即每一对 \((a_i,b_i)\) 的方案数的之积。暴力 \(\mathcal O(n)\) 计算是不能接受的,考虑优化。记 \(\displaystyle L=\max_{i=1}^n\min(a_i,b_i)\),则 \(x<L\) 时 \(k(x)=0\)。记 \(c_i\) 表示 \(\max(a_j,b_j)\leq i\) 的个数,前缀和即可 \(\mathcal O(n)\) 预处理,有 \(\displaystyle k(x)=[L\leq x]2^{c_x}\)。

预处理一下 \(2\) 的幂,即可 \(\mathcal O(n)\) 统计。这部分的贡献为:

\[-n\sum_{i=1}^mi\cdot(k(i)-k(i-1)) \]

显然求 \(\textit{suf}_i\) 的贡献可以把序列翻转后再求一遍,只考虑统计 \(\textit{pre}_i\) 的贡献。

考虑枚举 \(\textit{pre}_i=j\),计算方案数。后面的 \(n-i\) 个数随便选,\(2^{n-i}\) 种情况。

设 \(d_j\) 表示前缀最大值小于等于 \(j\) 的方案数。则加入 \((a_i,b_i)\) 时,有转移:

\[d_j\leftarrow d_j\cdot([a_i\leq j]+[b_i\leq j]) \]

于是可以 \(\mathcal O(n^2)\) 计算贡献,期望得分 70pts。

这个东西看起来不太能继续推导,考虑数据结构优化。

枚举加入 \((a_i,b_i)\) 是不能省略的,但是可以发现,修改操作其实是两个区间 \([0,\min(a_i,b_i)-1],[\max(a_i,b_i),m]\) 分别乘 \(0,2\)。启发我们使用线段树维护。

但是 \(\textit{pre}_i=j\) 的贡献形如 \(j(d_j-d_{j-1})\),直接用线段树维护 \(d\) 的话不好统计答案。因为 \(d_j\) 对于答案的贡献还涉及到 \(d_{j-1}\),区间边界的地方比较难做。考虑拆贡献,有 \(j\) 的贡献为 \(j(d_j-d_{j-1})=j\cdot d_j-(j+1)\cdot d_j\)。

需要注意的是,这里不能直接合并,不仅仅因为离散化,还因为统计答案的时候需要把加法贡献、减法贡献分开统计:因为我们实际上是把 \(j\) 的 \(-j\cdot d_{j-1}\) 的贡献转移到了 \(j-1\) 上维护,从而便于维护 \(d_j\)。

此时,\(i\) 的贡献即乘完之后 \([\min(a_i,b_i),m]\) 的加法标记和减去 \([\min(a_i,b_i)-1,m-1]\) 的减法标记和。

于是可以 \(\mathcal O(n\log n)\) 维护。

AC 代码

//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
constexpr const int N=5e5,P=1e9+7;
int n,a[N+1],b[N+1],pow2[N<<1|1];
int m,tmp[N*2+1+1];
void discrete(){
	m=0;
	for(int i=1;i<=n;i++){
		tmp[++m]=a[i];
		tmp[++m]=b[i];
	}
	sort(tmp+1,tmp+m+1);
	m=unique(tmp+1,tmp+m+1)-tmp-1;
	for(int i=1;i<=n;i++){
		a[i]=lower_bound(tmp+1,tmp+m+1,a[i])-tmp;
		b[i]=lower_bound(tmp+1,tmp+m+1,b[i])-tmp;
	}
}
struct segTree{
	struct node{
		int l,r;
		int add,minus,c,tag;
	}t[N<<3|1];
	
	void up(int p){
		t[p].add=(t[p<<1].add+t[p<<1|1].add)%P;
		t[p].minus=(t[p<<1].minus+t[p<<1|1].minus)%P;
	}
	void build(int p,int l,int r){
		t[p]={l,r};
		t[p].tag=1;
		if(l==r){
			t[p].c=(l>0);
			t[p].add=1ll*tmp[l]*t[p].c%P;
			t[p].minus=1ll*tmp[l+1]*t[p].c%P;
			return;
		}
		int mid=l+r>>1;
		build(p<<1,l,mid);
		build(p<<1|1,mid+1,r);
		up(p);
	}
	void down(int p){
		if(t[p].tag!=1){
			t[p<<1].add=1ll*t[p<<1].add*t[p].tag%P;
			t[p<<1].minus=1ll*t[p<<1].minus*t[p].tag%P;
			t[p<<1].c=1ll*t[p<<1].c*t[p].tag%P;
			t[p<<1].tag=1ll*t[p<<1].tag*t[p].tag%P;
			t[p<<1|1].add=1ll*t[p<<1|1].add*t[p].tag%P;
			t[p<<1|1].minus=1ll*t[p<<1|1].minus*t[p].tag%P;
			t[p<<1|1].c=1ll*t[p<<1|1].c*t[p].tag%P;
			t[p<<1|1].tag=1ll*t[p<<1|1].tag*t[p].tag%P;
			t[p].tag=1;
		}
	}
	void mul(int p,int l,int r,int x){
		if(l<=t[p].l&&t[p].r<=r){
			t[p].add=1ll*t[p].add*x%P;
			t[p].minus=1ll*t[p].minus*x%P;
			t[p].c=1ll*t[p].c*x%P;
			t[p].tag=1ll*t[p].tag*x%P;
			return;
		}
		down(p);
		if(l<=t[p<<1].r){
			mul(p<<1,l,r,x);
		}
		if(t[p<<1|1].l<=r){
			mul(p<<1|1,l,r,x);
		}
		up(p);
	}
	int queryAdd(int p,int l,int r){
		if(l<=t[p].l&&t[p].r<=r){
			return t[p].add;
		}
		int ans=0;
		down(p);
		if(l<=t[p<<1].r){
			ans+=queryAdd(p<<1,l,r);
		}
		if(t[p<<1|1].l<=r){
			ans+=queryAdd(p<<1|1,l,r);
		}
		ans%=P;
		return ans;
	}
	int queryMinus(int p,int l,int r){
		if(l<=t[p].l&&t[p].r<=r){
			return t[p].minus;
		}
		int ans=0;
		down(p);
		if(l<=t[p<<1].r){
			ans+=queryMinus(p<<1,l,r);
		}
		if(t[p<<1|1].l<=r){
			ans+=queryMinus(p<<1|1,l,r);
		}
		ans%=P;
		return ans;
	}
}t;
int ans;
void calc(){
	t.build(1,0,m);
	for(int i=1;i<=n;i++){
		t.mul(1,0,min(a[i],b[i])-1,0);
		t.mul(1,max(a[i],b[i]),m,2);
		ans=(ans+1ll*pow2[n-i]*(t.queryAdd(1,min(a[i],b[i]),m)-t.queryMinus(1,min(a[i],b[i])-1,m-1)))%P;
	} 
}
int main(){
	/*freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);*/
	
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	
	pow2[0]=1;
	for(int i=1;i<=N<<1;i++){
		pow2[i]=2ll*pow2[i-1]%P;
	}
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=n;i++){
		cin>>b[i];
	}
	discrete();
	calc();
	reverse(a+1,a+n+1);
	reverse(b+1,b+n+1);
	calc();
	reverse(a+1,a+n+1);
	reverse(b+1,b+n+1);
	static int c[N<<1|1];
	int L=0;
	for(int i=1;i<=n;i++){
		L=max(L,min(a[i],b[i]));
		c[max(a[i],b[i])]++;
	}
	for(int i=1;i<=m;i++){
		c[i]+=c[i-1];
	}
	auto k = [&](int x){
		if(x<L){
			return 0;
		}
		return pow2[c[x]];
	};
	for(int i=1;i<=m;i++){
		ans=(ans-1ll*tmp[i]*n%P*(k(i)-k(i-1)))%P;
	}
	for(int i=1;i<=n;i++){
		ans=(ans-1ll*pow2[n-1]*(tmp[a[i]]+tmp[b[i]]))%P;
	}
	ans=(ans+P)%P;
	cout<<ans<<'\n';
	
	cout.flush();
	
	/*fclose(stdin);
	fclose(stdout);*/
	return 0;
}
posted @ 2026-09-30 19:39  TH911  阅读(2)  评论(0)    收藏  举报