海亮寄 7.23

前言

业精于勤荒于嬉,行成于思毁于随

正文(加餐)

数据结构选讲,这是要 \(DAY^{-1}\) 的节奏

浅贴一个课件

关键词:区间拆分、单调栈维护极长极大连续段、离线扫描线线段树、区间计数(莫队主席树)、根号分治、复杂度平衡

今天不贴题单了,好多原题,没意思

(纯纯杂题选讲,啊不,杂 shi 选吃)

T1

显然不能枚举区间,故而枚举值域。进一步地,枚举中间区间的最小值

考虑预处理一些极长的 极大 / 极小 的连续段(单调栈)

由于只有 \(\min - \max > 0\) 才能对答案造成贡献

所以不妨把这个最小值枚举改成从小到大扫描线,每次动态插入一个相应的值

剩下就是答案计算基本功,开两个树状数组维护

具体怎么算的可以见代码

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5,MOD=9712176;
int n,a[N],rev[N],stk[N],tp;
int l[N],r[N];// mn
int L[N],R[N];// mx
inline void ADD(int &x,int y){
	x%=MOD,y%=MOD;x=(x+y)%MOD;
	return;
}
struct BIT{
	int c[N];
	inline int lb(int x){return x&(-x);}
	inline void add(int x,int v){
		for(int i=x;i<=n;i+=lb(i))ADD(c[i],v);
		return;
	}
	inline int ask(int x){
		int res=0;
		for(int i=x;i;i-=lb(i))ADD(res,c[i]);
		return res;
	}
	inline int qry(int l,int r){return (ask(r)-ask(l-1)+MOD)%MOD;}
}bit1,bit2;
inline void init(){
	for(int i=1;i<=n;i++)l[i]=0,r[i]=n+1;
	for(int i=1;i<=n;i++){
		while(tp&&a[stk[tp]]>a[i])r[stk[tp--]]=i;
		l[i]=stk[tp];stk[++tp]=i;
	}
	while(tp)stk[tp--]=0;
	for(int i=1;i<=n;i++)L[i]=0,R[i]=n+1;
	for(int i=1;i<=n;i++){
		while(tp&&a[stk[tp]]<a[i])R[stk[tp--]]=i;
		L[i]=stk[tp];stk[++tp]=i;
	}
	// cerr<<"mn:"<<endl;
	// for(int i=1;i<=n;i++)cerr<<l[i]<<' '<<r[i]<<endl;
	// cerr<<"mx:"<<endl;
	// for(int i=1;i<=n;i++)cerr<<L[i]<<' '<<R[i]<<endl;

	return;
}
signed main(){
	// freopen("in.txt","r",stdin);
	// freopen("out.txt","w",stdout);
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i],rev[a[i]]=i;
	init();int ans=0;
	for(int i=1;i<=n;i++){
		int p=rev[i];
		int ls=(bit2.ask(l[p])*i%MOD-bit1.ask(l[p])+MOD)%MOD;
		int rs=(bit2.qry(r[p],n)*i%MOD-bit1.qry(r[p],n)+MOD)%MOD;
		ADD(ans,(ls*rs%MOD*(p-l[p])%MOD*(r[p]-p)%MOD));
		bit1.add(p,i*(p-L[p])%MOD*(R[p]-p)%MOD);
		bit2.add(p,(p-L[p])*(R[p]-p)%MOD);
	}
	cout<<ans<<'\n';
	return 0;
}

T2

板,速切,代码不贴

T3

板,不会,此生不写

T4

神秘卡常题,人话就是把修改搞成矩阵的形式,剩下的全是基本功

就这种出纯数据结构还卡常的题的出题人怎么还没有趋势

代码懒得贴(简直写成一坨了)

T5

线段树优化建图板子

T6

感觉题意理解是一道大关

大概是给出 \(n\) 区间 \((a_i,b_i)\),问你存不存在两 区间 \(i,j\),使得 \(a_i \cap a_j = \varnothing \ \land \ b_i \cap b_j \neq \varnothing\)

考虑把一对区间进行拆分(保留第二个区间,即 \(b_i\),拆分第一个区间,即 \(a_i\)

然后把第一个区间的左右端点丢到数轴上(或者看成时间戳),打一个标记用于区分左端点和右端点

然后就对这个数轴进行扫描线,并用两个 multiset 分别维护第二区间左端点的最大值 \(mx\),以及第二区间右端点的最小值 \(mn\)

首先如果扫描到某区间的右端点,那么直接它将不再对后面造成影响,直接在 multiset 上删除

如果扫描到左端点且 multiset 是空的,那么直接插入,直接访问数轴上下一个标记点

如果扫描到左端点且 multiset 不是空的,那么开始判断

显然答案为否的充要条件是当前区间的右端点小于 \(mx\) 或者当前区间的左端点大于 \(mn\)

如果答案没有判定为否,那么把这个区间塞到 multiset 中,参与之后若干轮的检查

代码就是直接模拟上述分析即可

(题意被云落弱化了一下,实际上原题需要交换 \(a_i,b_i\) 再做一遍)

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define vn vector<node>
#define pb push_back
#define lwbd lower_bound
using namespace std;
const int N=1e5+5;
int n,sa[N],ea[N],sb[N],eb[N];bool flag=true;
struct node{
    int t,l,r,o;
    friend bool operator<(node x,node y){
		return x.t!=y.t?x.t<y.t:x.o<y.o;
	}
};
inline void solve(){
    multiset<int> st,ed;vn vec;
    for(int i=1;i<=n;i++){
        vec.pb({sa[i],sb[i],eb[i],1});
        vec.pb({ea[i]+1,sb[i],eb[i],0});
    }
    sort(vec.begin(),vec.end());
    for(int i=0;i<2*n;i++){
        if(!vec[i].o){
            st.erase(st.find(vec[i].l));
            ed.erase(ed.find(vec[i].r));
        }else{
            if(st.size()){
                ll mx=*(st.rbegin()),mn=*(ed.begin());
                if(mx>vec[i].r||mn<vec[i].l){flag=false;return;}
            }
            st.insert(vec[i].l);ed.insert(vec[i].r);
        }
    }
    return;
}
inline void trans(){
    swap(sa,sb),swap(ea,eb);
    return;
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++)cin>>sa[i]>>ea[i]>>sb[i]>>eb[i];
    solve();trans();solve();
    cout<<(flag?"YES\n":"NO\n");
    return 0;
}

T7

根号分治,大于等于阈值直接暴力,小于阈值预处理一手后缀和

代码懒得贴,就三十来行

T8

对于 \(k \le n \sqrt{n}\) 的情况,暴力!

对于 \(k> \sqrt{n}\),答案 \(\le \frac{n}{k}\)(显然 \(O(\sqrt{n})\) 量级),并且答案有单调性

所以二分随便做

时间复杂度 \(O(n \sqrt{n} \log n)\),这个 \(\log n\) 可以塞到根号里面

(据说还有神秘的主席树做法?)

T9

莫队板子

T10

莫队板子 \(\times 2\),需要用根号分治的手段平衡复杂度

(差点就写成暴力复杂度了……)

T11

手模几组数据,把一个值作为最小值的极长连续段给抓出来,连续段可以转化为区间个数。经过一顿观察,发现这样的值会给答案序列做一个区间加一个“梯形状物”,拐点与左右两端延伸的距离强相关

可以树状数组,但查询在修改之后,所以可以二阶差分

T12

分块,好无聊的题

根号分治显然,大于等于阈值的部分暴力,重点在于如何处理小于阈值的部分

其实不难,类似前面的一道题目,可以维护 \(f_{i,j}\) 表示 \(x=i,y=j\) 的累加总和

然后会推出一个式子,但单词查询是 \(O(n)\)

容易发现查询是一个区间的形式,直接前缀和优化

感觉只能说这么多,没有别的东西可以讲,实现上还有一点边角料

良心发现贴个代码吧……

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+5,B=450,MOD=1e9+7;
int n,m,siz,tot,L[B],R[B],blk[N];
ll a[N],sum[B],f[B][B];
inline void build(){
	siz=sqrt(n),tot=n/siz;
	for(int i=1;i<=tot;i++)L[i]=(i-1)*siz+1,R[i]=i*siz;
	if(R[tot]<n){tot++;L[tot]=R[tot-1]+1,R[tot]=n;}
	for(int i=1;i<=tot;i++)for(int j=L[i];j<=R[i];j++)blk[j]=i;
	return;
}
inline void upd(int x,int y,int z){
    if(x>=siz){
        for(int i=y;i<=n;i+=x)a[i]+=z,sum[blk[i]]+=z;
        return;
    }
    for(int i=y;i<=x;i++)f[x][i]+=z;
    return;
}
inline ll ask(int l,int r){
    ll res=0ll;
    for(int i=1;i<=tot;i++){
        res+=(f[i][r%i]-f[i][(l-1)%i]);
        res+=(f[i][i]*(r/i-(l-1)/i));
    }
    if(blk[l]==blk[r]){
        for(int i=l;i<=r;i++)res+=a[i];
        return res;
    }
    for(int i=blk[l]+1;i<=blk[r]-1;i++)res+=sum[i];
    for(int i=l;i<=R[blk[l]];i++)res+=a[i];
    for(int i=L[blk[r]];i<=r;i++)res+=a[i];
    return res;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
    cin>>n>>m;build();
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)sum[blk[i]]+=a[i];
    for(int i=1;i<=m;i++){
        int opt;cin>>opt;
        if(opt==1){int x,y,z;cin>>x>>y>>z;upd(x,y,z);}
        else{int l,r;cin>>l>>r;cout<<ask(l,r)%MOD<<'\n';}
    }
	return 0;
}

无聊至极

后记

世界孤立我任它奚落

完结撒花!

posted @ 2025-07-23 18:18  sunxuhetai  阅读(8)  评论(0)    收藏  举报