11.21模拟赛

T1

分层图板子,代码在 luogu。

T2

给定长度为 \(n\) 的数列 \(a\),维护 \(m\) 次操作,每次操作包括以下两种:

  • 1 x:要求输出包含 \(a_x\) 的最大子段和,即求 \(\max_{1 \leq l \leq x \leq r \leq n} \left\{ \sum_{i=l}^r a_i \right\}\)
  • 2 x:将所有 \(a_i\) 加上 \(x\)

对于全部数据:\(1≤n,m≤2×10^5,-10^7≤a_ i≤10^7,op∈\{1,2\}\),对于操作一有 \(1≤x≤n\),对于操作二有 \(-10^7≤x≤10^7\)

题解

解法一

可以转化为只有一种操作“假如全局加 \(k\),求包含 \(x\) 的最大子段和”。

如果解决“假如全局加 \(k\),求从 \(x\) 开始的最大子段和”,自然就能解决“假如全局加 \(k\),求以 \(x\) 结尾的最大子段和”,相加再减去 \(a_{x}+k\) 即可得到答案。

\(f_{i} = (a_{i} + k) + \max\{f_{i+1}, 0\}\)。如果 \(\max\)\(f_{i+1}\) 就连接一条 \(i+1\)\(i\) 的边,否则不连边,得到若干条链。设 \(C_{i}\) 表示 \(i\) 所在链中在 \(i\) 后面的位置的集合,显然有 \(f_{i} = \sum_{j \in C_{i}} (a_{j} + k) = \text{siz}(i) \times k + \text{val}(i)\),其中 \(\text{siz}(i) = |C_{i}|\)\(\text{val}(i) = \sum_{j \in C_{i}} a_{j}\)

显然,当 \(k\) 单调递增时,\(f_{i}\) 单调递增。我们按 \(k\) 递增离线,则所有转移式中 \(\max\) 必定先取 \(0\) 再取 \(f_{i+1}\),且两者的分界点是 \(k = -\frac{\text{val}(i)}{\text{siz}(i)}\) 时。用堆维护这些阈值,每次加边操作对 \(\text{siz}\), \(\text{val}\) 的影响是区间加,求 \(f\) 时只需要单点查,用树状数组维护即可。

code:

#include <bits/stdc++.h>
#define lowbit(x) (x&(-x))
#define int long long
using namespace std;
const int N=2e5+100;
int a[N],tot,n,sum,m,ans[N],tot2,sk[N],fa[N],val[N],siz[N],vis[N],vals[N],nxt[N];
map <int,int> mp;
struct que{
	int x,id;
};
vector <que> ask[N];
struct BIT{
	int tr[N];
	void update(int x,int val){
		while(x<N){
			tr[x]+=val;
			x+=lowbit(x);
		}
	}
	int query(int x){
		int res=0;
		while(x){
			res+=tr[x];
			x-=lowbit(x);
		}
		return res;
	}
	void clear(int x){
		while(x){
			tr[x]=0;
			x-=lowbit(x);
		}
	}
}tv,ts;
struct node{
	int u;
	double v;
	friend bool operator<(node x,node y){
		return x.v>y.v;
	}
};
priority_queue <node> q;

int find(int x){
	if(fa[x]==x) return x;
	return fa[x]=find(fa[x]);
}

void merge(int x,int y){
	int fx=find(x);
	int fy=find(y);
	if(fx==fy) return;
	fa[fx]=fy;
	val[fy]+=val[fx];
	siz[fy]+=siz[fx]; 
	nxt[fy]=nxt[fx];
}

signed main(){
	freopen("subarray.in","r",stdin);
	freopen("subarray.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	sk[++tot]=0;
	mp[0]=1;
	for(int i=1;i<=m;i++){
		int opt,x;
		cin>>opt>>x;
		if(opt==1) ask[mp[sum]].push_back(que{x,++tot2});
		else{
			sum+=x;
			if(!mp[sum]){
				mp[sum]=++tot; 
				sk[tot]=sum;
			} 
		}
	}
	sort(sk+1,sk+1+tot);
	for(int i=1;i<=n;i++){
		fa[i]=i;
		val[i]=a[i];
		siz[i]=1;
		nxt[i]=i;
		vals[i]=vals[i-1]+val[i];
		q.push(node{i,-1.0*val[i]/siz[i]});
	}
	for(int i=1;i<=tot;i++){
		if(ask[mp[sk[i]]].empty()) continue;
		while(!q.empty()){
			node t=q.top();
			if(t.v>sk[i]) break;
			q.pop();
			if(vis[t.u]) continue;
			vis[t.u]=1;
			if(t.u==1) continue;
			int to=find(t.u-1);
			merge(t.u,to);
			q.push(node{to,-1.0*val[to]/siz[to]});
		}
		for(que j:ask[mp[sk[i]]]){
			int x=j.x;
			int fx=find(x);
			ans[j.id]+=(nxt[fx]-x+1)*sk[i]+(vals[nxt[fx]]-vals[x-1]);
		}
	}
	while(!q.empty()) q.empty();
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n;i++){
		fa[i]=i;
		val[i]=a[i];
		siz[i]=1;
		nxt[i]=i;
		vals[i]=vals[i-1]+val[i];
		q.push(node{i,-1.0*val[i]/siz[i]});
	}
	for(int i=1;i<=tot;i++){
		if(ask[mp[sk[i]]].empty()) continue;
		while(!q.empty()){
			node t=q.top();
			if(t.v>sk[i]) break;
			q.pop();
			if(vis[t.u]) continue;
			vis[t.u]=1;
			if(t.u==n) continue;
			int to=find(t.u+1);
			merge(t.u,to);
			q.push(node{to,-1.0*val[to]/siz[to]});
		}
		for(que j:ask[mp[sk[i]]]){
			int x=j.x;
			int fx=find(x);
			ans[j.id]+=(x-nxt[fx]+1)*sk[i]+(vals[x]-vals[nxt[fx]-1]);
			ans[j.id]-=(a[x]+sk[i]);
		}
	}
	
	for(int i=1;i<=tot2;i++) cout<<ans[i]<<'\n';
	return 0;
}

解法二

\(Ynoi\) 的末日三问中可以得到启发,我们把询问离线挂到每个点上。然后从左往右查询每个询问。接下来,我们维护一个二维坐标,横坐标为每个点的具体下标,纵坐标为每个点到当前末尾点的后缀和。接下来我们发现,如果 \(i-1,i,i+1\) 这三个横坐标对应的点是凹的,那么在加上 \(k\) 之后,也一定是凹的,因为在每次 \(x\) 的增加下,每两个点之间的 \(k\) 只也会增加 \(k\),但是大小关系不变,因为是凹陷的所以中间的点一定不优,我们直接用李超树或者什么其他东西维护这个凸包就好了,对于每个询问因为凸性不变所以直接加上个权值二分下 \(k\) 值就好了。

code:

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef long double ld;

const int N=2e5+10;

struct ques{
    int pl,id; ll x;
}que[N];

vector<pair<ll,int> > vec[N];

int n,m,cnt=0;

ll a[N],ans[N];

struct node{
    int x; ll y;
    inline node operator - (const node &t) const { return {x-t.x,y-t.y};}
    inline ll operator * (const node &t) const{ return y*t.x-x*t.y;}
}hl[N];

int tot=0;

int main(){
    #ifndef Klein
        freopen("subarray.in","r",stdin);
        freopen("subarray.out","w",stdout);
    #endif
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];

    int opt,x; ll sum=0;
    while(m--){
        cin>>opt>>x;
        if(opt==1) que[++cnt]={x,cnt,sum};
        else sum+=x;
    }
    sort(que+1,que+1+cnt,[](ques a,ques b){ return a.x<b.x;});

    ll delt=que[1].x;
    for(int i=1;i<=n;i++) a[i]+=delt;
    for(int i=1;i<=cnt;i++){
        que[i].x-=delt;
        ans[que[i].id]=a[que[i].pl]+que[i].x;
        vec[que[i].pl].push_back({que[i].x,que[i].id});
    }

    tot=sum=0;
    for(int i=1;i<=n;i++){
        if(tot){
            for(auto j:vec[i]){
                ll x=j.first; int id=j.second;
                int l=1,r=tot-1,mid,res=tot;
                while(l<=r){
                    mid=(l+r)>>1;
                    if(hl[mid].y+(i-hl[mid].x)*x>=hl[mid+1].y+(i-hl[mid+1].x)*x) r=mid-1,res=mid;
                    else l=mid+1;
                }
                ans[id]+=max(0ll,hl[res].y+sum+(i-hl[res].x)*x);
            }    
        }
        node now={i,-sum}; sum+=a[i];
        if(tot<2) hl[++tot]=now;
        else{
            while(tot>=2&&(hl[tot]-now)*(hl[tot-1]-hl[tot])>=0) tot--;
            hl[++tot]=now;
        }
    }

    tot=sum=0;
    for(int i=n;i;i--){
        if(tot){
            for(auto j:vec[i]){
                ll x=j.first; int id=j.second;
                int l=1,r=tot-1,mid,res=tot;
                while(l<=r){
                    mid=(l+r)>>1;
                    if(hl[mid].y+(hl[mid].x-i)*x>=hl[mid+1].y+(hl[mid+1].x-i)*x) r=mid-1,res=mid;
                    else l=mid+1;
                }
                ans[id]+=max(0ll,hl[res].y+sum+(hl[res].x-i)*x);
            }
        }
        node now={i,-sum}; sum+=a[i];
        if(tot<2) hl[++tot]=now;
        else{
            while(tot>=2&&(hl[tot]-now)*(hl[tot-1]-hl[tot])<=0) tot--;
            hl[++tot]=now;
        }
    }

    for(int i=1;i<=cnt;i++) cout<<ans[i]<<'\n';
    return 0;
}

T3

给定一个左右部各 \(n\) 个点的二分图。左部第 \(i\) 个点为 \(L_i\),右部第 \(i\) 个点为 \(R_i\)

对于左部的第 \(i\) 个点,在图上有边 $(L_i, R_{l_i}), (L_i, R_{l_i+1}), \ldots, (L_i, R_{r_i}) $ 。

我们称一个匹配是好的,当且仅当不存在两条匹配边 \((L_a, R_b)\)\((L_c, R_d)\),使得 \(a < c\)\(b > d\)
你需要求出最大的好匹配的大小。

对于 \(100\%\) 的数据,\(n \le 10^5, 1 \le l_i \le r_i \le n\)

题解

一眼秒了正解但是不会平衡树。

首先设计一个显然的 DP,\(f_{i,j}\) 表示前 \(i\) 个人最后一个放在 \(\le j\) 的位置的最大答案。

发现转移形如对于 \(i\in[l,r]~f_{i,j}=f_{i-1,j-1}+1\),然后再对序列每个点赋值成前缀最大值。

发现这个东西在差分数组下只有 \(0/1\),然后第一步在差分数组的转移可以看成 \([l,r-1]\to [l+1,r]\),然后把 \(l\) 上的值赋值为 \(1\)

下一步就是如果原来数组 \(r\) 位置上的数是 \(0\),那么把 \([r+1,n]\) 上的第一个 \(1\) 改为 \(0\),否则不做调整。这一步是为了满足前缀最大值的限制,手模一下就知道为什么了。为此学习了 \(\text{pb\_ds}\)

code:

#include<bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#define fi first
#define se second
#define int long long
#define pdi pair<double,int>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
using namespace __gnu_pbds;
using namespace std;
tree<pdi,null_type,less<pdi>,rb_tree_tag,
tree_order_statistics_node_update>tr,trr;
int n,sum;
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	freopen("match.in","r",stdin);
	freopen("match.out","w",stdout);
	cin>>n;tr.insert({0,0});
	rep(i,1,n+1) tr.insert({i*(1ll<<20),0});
	rep(i,1,n){
		int l,r;cin>>l>>r;
		pdi t=*tr.find_by_order(r);
		trr.erase(t);tr.erase(t);
		pdi t1=*tr.find_by_order(l);
		pdi t4=*tr.find_by_order(l-1);
		pdi t2={(t1.fi+t4.fi)*1.0/2.0,1};
	    tr.insert(t2);trr.insert(t2);	
		pdi t3=*trr.upper_bound(t);	
		if(t.se==0&&trr.upper_bound(t)!=trr.end()){
			tr.erase(t3),trr.erase(t3);
			t3.se=0;tr.insert(t3);
		}
	}rep(i,1,n){sum+=(*tr.find_by_order(i)).se;}
	cout<<sum;
	return 0;
}

T4

这是一道原题不知道为啥又来考。直接 copy from 过去的我。

有一张 \(n \times n\) 的网格图, 点的标号从 \((1,1)\)\((n, n)\)

对于所有 \(1 \leq i<n, 1 \leq j \leq n\), 有一条 \((i, j)\)\((i+1, j)\) 的边权为 1 的无向边。

对于所有 \(1 \leq i \leq n, 1 \leq j<n\), 有一条 \((i, j)\)\((i, j+1)\) 的边权为 1 的无向边。

此外, 还有 \(m\) 个四元组 \(\left(a_{i}, b_{i}, c_{i}, d_{i}\right)\), 保证 \(a_{i} \neq c_{i}\)\(b_{i} \neq d_{i}\), 对于所有 \(1 \leq i \leq m\),有一条 \((a, b)\)\((c, d)\) 的边权为 \(\left|a_{i}-c_{i}\right|+\left|b_{i}-d_{i}\right|-1\) 的无向边。

求所有 \(\frac{n^{2}\left(n^{2}-1\right)}{2}\) 对不同的点对之间的最短路之和, 答案对 998244353 取模。

对于 \(100 \%\) 的数据, \(1 \leq n \leq 10^{9}, 1 \leq m \leq 500\)

题解

贴一下官方题解原题link

对于一个点对 \((x_1,y_1)\)\((x_2,y_2)\)

如果 \(x_1 = x_2\)\(y_1 = y_2\),其长度仍然为 \(|x_1 - x_2| + |y_1 - y_2|\)

不失一般性(WLOG),假设 \(x_1 < x_2\)\(y_1 < y_2\)

经证明,曼哈顿路径可通过递增路径序列进行优化,其中每个后续的“快速桥”都位于前一个的右上方。我们定义 \(f(x_1, y_1, x_2, y_2)\) 为在点 \((x_1, y_1)\)\((x_2, y_2)\) 之间的最长递增路径的长度。

\((x_1, y_1)\)\((x_2, y_2)\) 之间的最短路径长度为 \(|x_1 - x_2| + |y_1 - y_2| - f(x_1, y_1, x_2, y_2)\)

因此,答案等于所有单元格对之间的曼哈顿距离之和(该和等于 \({n^4(n + 1)}-\frac{n^3(n + 1)(2n + 1)}{3}\))减去所有单元格对的 \(f(x_1, y_1, x_2, y_2)\) 之和。

\(f(x_1, y_1, x_2, y_2)\) 的总和可分别针对 \(y_1 < y_2\)\(y_1 > y_2\) 的单元格对进行计算。

现在我们需要计算所有满足 \(x_1 < x_2\)\(y_1 < y_2\) 的单元格对 \((x_1, y_1)\)\((x_2, y_2)\) 之间的 \(f(x_1, y_1, x_2, y_2)\) 之和。

\(dp_{i,j}\) 为以第 \(i\) 个快速桥为起点、以第 \(j\) 个快速桥为终点的最长递增路径的长度。该值可在 \(O(n^3)\) 时间内轻松求得。

我们对所有桥的左下单元格坐标进行压缩,然后在压缩后的网格中从右向左、从上到下迭代单元格。在迭代过程中,我们计算 \(f_{x,y,i}\) 的值——即 \(x\) 坐标为 \(x\)\(y\) 坐标为 \(y\) 且以第 \(i\) 个快速桥为终点的最长递增路径的长度。它们可通过 \(f_{x+1,y,i}\)\(f_{x,y+1,i}\) 以及满足 \(x1_t = x\)\(y1_t = y\)\(dp_{t,i}\) 的最大值来计算。显然,只需维护 \(f\) 的最后一层,因此仅需 \(O(n^2)\) 的内存。总计算时间为 \(O(n^3)\)

在迭代过程中,我们计算满足 \((x_1, y_1)\) 位于压缩网格单元格 \((x,y)\) 中的 \(f(x_1, y_1, x_2, y_2)\) 之和。需注意,对于所有 \(x_2 \geq x2_i\)\(y_2 \geq y2_i\),有 \(f(x_1, y_1, x_2, y_2) \geq f_{x,y,i}\)

我们找出所有满足 \(f_{x,y,i} = t\) 的矩形 \([x2_i, +\infty) \times [y2_i, +\infty)\) 的并集面积。该面积等于 \(f(x_1, y_1, x_2, y_2) \geq t\) 的数量。因此,我们只需对所有 \(t\) 求和这些面积即可。

为求得每个值 \(t\) 对应的并集面积,我们可以按 \(x\) 递增的顺序迭代所有点 \((x2_i, y2_i)\),并存储之前最低的点(即 \(y\) 最小的点)。通过添加新点,面积可在 \(O(1)\) 时间内重新计算。我们可以在算法开始前对所有点 \((x2_i, y2_i)\) 进行排序,因此对于压缩网格的所有单元格 \((x,y)\),这部分解决方案的时间复杂度为线性。

总的时间复杂度为 \(O(n^3)\),内存复杂度为 \(O(n^2)\)

#include <iostream>
#include <cstdio>
#include <map>
#include <cstring>
#include <algorithm>
#define mod 998244353
using namespace std;
int tot,V;
int S,ans;
struct WORK{
	int tot,xa[505],ya[505],xb[505],yb[505];
	int n,m,pos_x[1005],pos_y[1005];
	map<int,int> id_x,id_y;
	int dis[505][505];
	int id[505];
	int f[505],h[505];
	void bubble_sort1(){
		for (int i=1;i<=tot;i++)
			for (int j=1;j<tot;j++)
				if (ya[j]>ya[j+1]){
					swap(xa[j],xa[j+1]);
					swap(ya[j],ya[j+1]);
					swap(xb[j],xb[j+1]);
					swap(yb[j],yb[j+1]);
				}
		return;
	}
	void bubble_sort2(){
		for (int i=1;i<=tot;i++)
			for (int j=1;j<tot;j++)
				if (yb[id[j]]>yb[id[j+1]])swap(id[j],id[j+1]);
		return;
	}
	void work(){
		id_x[V]=1,pos_x[++n]=V;
		id_y[V]=1,pos_y[++m]=V;
		for (int i=1;i<=tot;i++){
			if (id_x[xa[i]]==0){
				id_x[xa[i]]=1;
				pos_x[++n]=xa[i];
			}
			if (id_y[ya[i]]==0){
				id_y[ya[i]]=1;
				pos_y[++m]=ya[i];
			}
			if (id_x[xb[i]-1]==0){
				id_x[xb[i]-1]=1;
				pos_x[++n]=xb[i]-1;
			}
			if (id_y[yb[i]-1]==0){
				id_y[yb[i]-1]=1;
				pos_y[++m]=yb[i]-1;
			}
		}
		sort(pos_x+1,pos_x+1+n);
		for (int i=1;i<=n;i++)id_x[pos_x[i]]=i;
		sort(pos_y+1,pos_y+1+m);
		for (int i=1;i<=m;i++)id_y[pos_y[i]]=i;
		for (int i=1;i<=tot;i++){
			xa[i]=id_x[xa[i]];
			ya[i]=id_y[ya[i]];
			xb[i]=id_x[xb[i]-1]+1;
			yb[i]=id_y[yb[i]-1]+1;
		}
		bubble_sort1();
		for (int i=1;i<=tot;i++)
			for (int j=i+1;j<=tot;j++)
				if (xb[i]<=xa[j]&&yb[i]<=ya[j])dis[i][j]=1;
		for (int k=1;k<=tot;k++)
			for (int i=1;i<k;i++)
				for (int j=k+1;j<=tot;j++)
					if (dis[i][k]>0&&dis[k][j]>0)dis[i][j]=max(dis[i][j],dis[i][k]+dis[k][j]);
		for (int i=1;i<=tot;i++)id[i]=i;
		bubble_sort2();
		for (int i=n;i>=1;i--){
			memset(f,0,sizeof(f));
			int p=tot;
			for (int j=m;j>=1;j--){
				while(p>=1&&ya[p]>=j){
					if (xa[p]>=i){
						f[p]=1;
						for (int k=p+1;k<=tot;k++)
							if (xa[k]>=i&&dis[p][k]>0)f[k]=max(f[k],dis[p][k]+1);
					}
					p--;
				}
				for (int k=1;k<=tot;k++)h[k]=V+1;
				int s=0;
				for (int k=1;k<=tot;k++){
					int t=id[k];
					if (f[t]>0){
						int _f=f[t],_x=xb[t],_y=yb[t];
						if (pos_x[_x]<h[_f]){
							s=(s+1ll*(h[_f]-(pos_x[_x-1]+1))*(V-(pos_y[_y-1]+1)+1))%mod;
							h[_f]=pos_x[_x-1]+1;
						}
					}
				}
				ans=(ans+1ll*(pos_x[i]-pos_x[i-1])*(pos_y[j]-pos_y[j-1])%mod*s)%mod;
			}
		}
		return;
	}
}work1,work2;
int main(){
	freopen("path.in","r",stdin);
	freopen("path.out","w",stdout);
	cin>>tot>>V;
	S=(1ll*V*(V+1)/2%mod*(-V-1)%mod+mod)%mod;
	S=(S+1ll*V*(V+1)%mod*(2*V+1)%mod*((mod+1)/3)%mod)%mod;
	S=1ll*S*V%mod*V%mod*2%mod;
	for (int i=1;i<=tot;i++){
		int xa,ya,xb,yb;
		cin>>xa>>ya>>xb>>yb;
		if (xa>xb)swap(xa,xb),swap(ya,yb);
		if (ya<yb){
			int t=++work1.tot;
			work1.xa[t]=xa,work1.ya[t]=ya,work1.xb[t]=xb,work1.yb[t]=yb;
		}
		else{
			int t=++work2.tot;
			work2.xa[t]=xa,work2.ya[t]=V-ya+1,work2.xb[t]=xb,work2.yb[t]=V-yb+1;
		}
	}
	work1.work();
	work2.work();
	cout<<(S-ans+mod)%mod<<endl;
	return 0;
}
posted @ 2025-11-21 19:43  NeeDna  阅读(16)  评论(0)    收藏  举报