20260721 - 分治 总结

前言

这次写的可能会稍微潦草一些喵喵。哦或者准确来说暑假训练这边都不会写那么好 qwq 因为实在没那么多时间喵。

我在 E 题创下了历史新纪录 12 发罚时(雾

分治

四个字解释:分而治之。

大概就是将一个区间划分为几个相同形式但更小规模的部分去递归解决,比较难的或者说比较重点的是怎么处理跨区间的,也就是拼接的部分。

唉描述不好,意会一下吧嗯。

A - 幂次方

不是很简单的简单题。

你弄个函数 \(\text{solve}(n)\),处理这个 \(n\) 的时候就拆出它的二进制拆分(从高到低)然后分治去做,注意处理一下什么地方要输出 + 以及 \(0,1,2\) 的边界喵。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
int n;
int read(){
    int su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
void solve(int num){
	if(!num){cout<<"0";return;}
	if(num==1){cout<<"2";return;}
	vector<int> g;g.clear();
	bool flag=0;
	for(int i=20;i>=0;i--)
	    if((num>>i)&1){
	    	if(flag)cout<<"+";else flag=1;
	    	if(i==1)cout<<"2";
	    	else if(i==0)cout<<"2(0)";
	    	else{cout<<"2(";solve(i);cout<<")";}
	    }
	return;
}
int main(){
    n=read();
    solve(n);cout<<"\n";
    return 0;
}

B - 逆序对

归并模版,不想讲了。处理的时候注意它是严格大于才算逆序对,所以你如果是在左边计数的话,等于号要放在左边。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 5e5+5;
int n,a[N],tmp[N];
LL Ans;
int read(){
    int su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
void solve(int l,int r){
	if(l>=r)return;
	int mid=(l+r)>>1;
	solve(l,mid);solve(mid+1,r);
	int p1=l,p2=mid+1;
	for(int i=l;i<=r;i++)
	    if((p2>r)||(p1<=mid&&a[p1]<=a[p2]))
		    Ans+=p2-mid-1ll,tmp[i]=a[p1++];
	    else tmp[i]=a[p2++];
	for(int i=l;i<=r;i++)a[i]=tmp[i];
	return;
}
int main(){
	n=read();
	for(int i=1;i<=n;i++)a[i]=read();
	solve(1,n);
	cout<<Ans<<"\n";
    return 0;
}

C - Permutation Transformation

直接模拟题意去构建这个树就行了,对一段当前处理到的区间找出其根也就是那个 \(\max\),然后以这个 \(\max\) 的位置为 \(mid\) 递归 \([l,mid-1]\)\([mid+1,r]\) 两个区间就行。注意事实维护深度 \(dep\) 数组。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 105;
int T,n,a[N],dep[N];
int read(){
    int su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
void solve(int l,int r){
	if(l>=r)return;
	int rt=0;
	for(int i=l;i<=r;i++)
	    if(a[i]>a[rt])rt=i;
	for(int i=l;i<=r;i++)
	    if(i!=rt)dep[i]++;
	solve(l,rt-1);solve(rt+1,r);
	return;
}
int main(){
	T=read();
	while(T--){
		n=read();
		for(int i=1;i<=n;i++)
		    a[i]=read(),dep[i]=0;
		solve(1,n);
		for(int i=1;i<=n;i++)
		    cout<<dep[i]<<" ";cout<<"\n";
	}
    return 0;
}

D - 地毯填补问题

一坨,给我调疯了。大概就是你去递归这一个矩形部分,然后判断这里的“障碍物”也就是已经被覆盖了(放过地毯了)或者不能被覆盖(公主站的地方)的地方在左上、右上、左下、右下中的哪个位置,对剩下三个位置的交界处那里放一个对应类型的地毯,然后递归去做就行了。

很难写,细节很多,反正不好调,代码也很丑,还有当时的调试痕迹,将就看吧。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
struct node{int x,y,id;};
int k,n,Px,Py,a[105][105];
vector<node> ans;
int read(){
    int su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
void solve(int sx,int sy,int ex,int ey,int Kx,int Ky){
	if(sx==ex&&sy==ey)return;
	int mx=(sx+ex)/2,my=(sy+ey)/2;
	//cout<<"("<<sx<<","<<sy<<") -> ("<<ex<<","<<ey<<") <"<<Kx<<","<<Ky<<">\n";
	if(Kx<=mx&&Ky<=my){
		node tmp;
		tmp.x=mx+1,tmp.y=my+1,tmp.id=1;
		//cout<<tmp.x<<" "<<tmp.y<<" "<<tmp.id<<"1!\n";
		ans.pb(tmp);
		solve(sx,sy,mx,my,Kx,Ky);
		solve(sx,my+1,mx,ey,mx,my+1);
		solve(mx+1,sy,ex,my,mx+1,my);
		solve(mx+1,my+1,ex,ey,mx+1,my+1);
	}
	else if(Kx<=mx&&Ky>my){
		node tmp;
		tmp.x=mx+1,tmp.y=my,tmp.id=2;
		//cout<<tmp.x<<" "<<tmp.y<<" "<<tmp.id<<"2!\n";
		ans.pb(tmp);
		solve(sx,sy,mx,my,mx,my);
		solve(sx,my+1,mx,ey,Kx,Ky);
		solve(mx+1,sy,ex,my,mx+1,my);
		solve(mx+1,my+1,ex,ey,mx+1,my+1);
	}
	else if(Kx>mx&&Ky<=my){
		node tmp;
		tmp.x=mx,tmp.y=my+1,tmp.id=3;
		//cout<<mx<<","<<my<<"!!!\n";
		//cout<<tmp.x<<" "<<tmp.y<<" "<<tmp.id<<"3!\n";
		ans.pb(tmp);
		solve(sx,sy,mx,my,mx,my);
		solve(sx,my+1,mx,ey,mx,my+1);
		solve(mx+1,sy,ex,my,Kx,Ky);
		solve(mx+1,my+1,ex,ey,mx+1,my+1);
	}
	else if(Kx>mx&&Ky>my){
		node tmp;
		tmp.x=mx,tmp.y=my,tmp.id=4;
		//cout<<tmp.x<<" "<<tmp.y<<" "<<tmp.id<<"4!\n";
		ans.pb(tmp);
		solve(sx,sy,mx,my,mx,my);
		solve(sx,my+1,mx,ey,mx,my+1);
		solve(mx+1,sy,ex,my,mx+1,my);
		solve(mx+1,my+1,ex,ey,Kx,Ky);
	}
	return;
}
int main(){
	k=read(),n=(1<<k);
	Px=read(),Py=read();
	solve(1,1,n,n,Px,Py);
	
	for(int i=0;i<ans.size();i++){
		int x=ans[i].x,y=ans[i].y,id=ans[i].id;
		//if(id==1)a[x][y]++,a[x-1][y]++,a[x][y-1]++;
		//if(id==2)a[x][y]++,a[x-1][y]++,a[x][y+1]++;
		//if(id==3)a[x][y]++,a[x+1][y]++,a[x][y-1]++;
		//if(id==4)a[x][y]++,a[x+1][y]++,a[x][y+1]++;
		cout<<x<<" "<<y<<" "<<id<<"\n";
	}
	
	//for(int i=1;i<=n;cout<<"\n"&&i++)
	//for(int j=1;j<=n;j++)cout<<a[i][j]<<" ";
    return 0;
}

E - Equivalent Strings

模拟题意去判断就行了,加逻辑优化才能跑快点(否则复杂度好像有点点问题),可我一直卡不过去。

最后的死因是 substr 常数太大了,换掉就好了,我晕。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
string a,b;
int read(){
    int su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
bool check(int l1, int r1, int l2, int r2) {
    int len=r1-l1;
    for(int i=0;i<len;i++) 
        if(a[l1+i]!=b[l2+i])return 0;
    return 1;
}
bool Is_same(int l1,int r1,int l2,int r2) {
    int len=r1-l1; 
    if(len&1) return check(l1,r1,l2,r2); 
    int mid1=(l1+r1)>>1,mid2=(l2+r2)>>1;
    return ((Is_same(l1,mid1,l2,mid2)&&Is_same(mid1,r1,mid2,r2))
           ||(Is_same(l1,mid1,mid2,r2)&&Is_same(mid1,r1,l2,mid2)));
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr),cout.tie(nullptr);
    cin>>a>>b;
    int n=a.size();
    if(Is_same(0,n,0,n))cout<<"YES\n";
    else cout<<"NO\n";
    return 0;
}

F - Merge Sort

还比较好想的一个题。

首先你发现每次要增加 \(k\) 一定是增加 \(2\) 个,并且 \(k\) 的基础值是 \(1\),所以 \(k\) 肯定是个奇数。那如果是偶数就直接倒闭了。

然后你去递归做,维护四个值 \(l,r,vl,vr\),分别表示当前考虑的序列区间 \([l,r]\) 和值域区间 \([vl,vr]\)。如果当前 \(k\) 数量已经够了就直接将当前分配到的值域升序赋值给这段区间,这样就不会带来更多的调用次数了;否则你就把区间按照题目的说法一分为二,并且将值域大的分到左边、值域小的分到右边(否则你递归下去的理由就不充足了),分治去做就行了。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 1e5+5;
int n,k,a[N],nowcnt=1;
int read(){
    int su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
void solve(int l,int r,int vl,int vr){
	if(nowcnt>=k||l+1==r){
		//cout<<l<<" "<<r<<" "<<vl<<","<<vr<<"\n"; 
		for(int i=l;i<r;i++)a[i]=i-l+vl;
		return;
	}
	if(nowcnt<k){
		nowcnt+=2;
		int mid=(l+r)>>1;
		int vmid=(vl+vr+1)>>1;
		solve(l,mid,vmid,vr);
		solve(mid,r,vl,vmid);
	}
	return;
}
int main(){
	n=read(),k=read();
	solve(0,n,0,n);
	if(nowcnt!=k){cout<<"-1\n";return 0;}
	for(int i=0;i<n;i++)
	    cout<<a[i]+1<<" ";cout<<"\n";
    return 0;
}

G - Secret Cow Code S

你的意思是这题是橙题吗?

正难则反的一个思想,我们去定位这第 \(N\) 个到底是原字符串中的哪一个。我们找到能覆盖这 \(N\) 个的最长区间的一半(也就是我们找到的这个区间复制一遍向右旋转恰好能覆盖到 \(N\) 位置),假设长度为 \(k\),则让 \(N\) 减去 \(k\)。但是还不够,因为我们向右旋转了,所以还需要再 \(-1\)。如果变成 \(0\) 了那就变成 \(|s|\) 也就是序列长度。直到这个 \(N \le |s|\) 我们就停止这个操作并输出即可。

讲的有点乱,看代码。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
LL n,k;
string s;
LL read(){
    LL su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}

int main(){
	cin>>s;k=read();
	n=s.size(),s=" "+s;
	while(k>n){
		LL len=n;
		while(len*2<k)len*=2;
		k-=len+1;
		if(!k)k=len;
	}
	cout<<s[k]<<"\n"; 
    return 0;
}

H - Code For 1

非常暴力但很牛逼的做法。

就是你枚举 \([l,r]\) 这个区间的每个位置 \(i\),因为是 \(10^5\) 级别的所以可以接受,然后我们去算出位置 \(i\) 具体的值然后累加。

行,说这个值怎么算。很简单的分治,首先要经过一些打表并不是那么严谨的推理发现当初始元素为单独一个 \(n\) 时,假设 \(k\) 是最小的 \(2^k \ge n\) 的值,则长度是 \(2^k-1\)。这个证起来也比较简单这里不展开描述了,那么中间值就是 \(2^{k-1}\)。而中间值就是 \(n \bmod 2\),左边右边都是 \(\lfloor \frac{n}{2} \rfloor\) 的子问题,分治做就行啦。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
LL n,l,r,Ans;
LL read(){
    LL su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
LL sol_count(LL n,LL u){
    LL tmp=n,mid=1;
    while(tmp>1)tmp/=2ll,mid*=2ll;
    if(u<mid)return sol_count(n/2,u);
    else if(u>mid)return sol_count(n/2,u-mid);
    else return n%2;
}
int main(){
    n=read(),l=read(),r=read();
    for(LL i=l;i<=r;i++)
        Ans+=sol_count(n,i);
    cout<<Ans<<"\n";
    return 0;
}

I - Max GEQ Sum

我们可以维护一个从栈顶到栈底严格递增的单调栈。

当然,这个东西不是用来摆烂的,是有用的。怎么个作用呢?我们顺序枚举每个 \(a_i\),如果栈顶不大于 \(a_i\),说明从栈顶到 \(i\) 这段区间的最大值是 \(a_i\),就可以比较这段区间的和与 \(a_i\) 的值,然后把栈顶干出去。重复这个过程直到栈顶比 \(a_i\) 大。

那当栈顶比 \(a_i\) 大的时候,说明这段区间最大值是栈顶值,还是可以比较。最后把 \(a_i\) 塞进栈。

如果对每个 \(i\) 做完这遍操作都还没有出现不合法的,那就一定合法了;否则就一定不合法了。

这就有问题了,栈里其他值和当前 \(a_i\) 构成的区间判都不判啊?确实不需要判,因为我们可以直接根据上面两个的情况得到后面的每个情况,所以是不需要的。很简单的式子。

代码很短。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 2e5+5;
LL T,n,a[N],sum[N];
LL stk[N],top;
bool flag;
LL read(){
    LL su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}

int main(){
    T=read();
    while(T--){
        n=read();sum[0]=0;
        for(int i=1;i<=n;i++)
            a[i]=read(),sum[i]=sum[i-1]+a[i];
        top=0,flag=1;
        for(int i=1;i<=n;i++){
            while(top&&a[stk[top]]<=a[i]){
                if(sum[i]-sum[stk[top]-1]>a[i])flag=0;//NO
                top--;//弹出栈顶
            }
            if(top&&sum[i]-sum[stk[top]-1]>a[stk[top]])flag=0;//NO
            stk[++top]=i;
            if(!flag)break;//已经 NO 了没必要继续
        }
        cout<<(flag?"YES\n":"NO\n");
    }
    return 0;
}

J - 平面最近点对(加强版)

我们充分发扬人类智慧:

可惜鸭子无法拥有人类智慧,哎呀,好难受。

我们先对所有点按 \(x\) 升序排序,然后去用类似归并排序的思想做,但这里归并排序的是以 \(y\) 为关键字的升序。当然你还要拿到两个子区间的最近点对距离,记为 \(dis\)。重点在于合并两个部分的地方,我们找出 \(x_{mid}\) 也就是这一段数中最中间的 \(x\) 值作为一个比较值,如果一个点的 \(x\)\(x_{mid}\) 的距离(不考虑 \(y\))不大于 \(dis\) 就可以塞到临时数组里考虑,然后枚举临时数组中每个点对让它们的值互相更新即可(可以做一些剪枝,比如超过 \(dis\) 了可以直接退出循环)。可以证明这个部分的时间是常数级别的,几乎可以忽略不计。

哦,然后就做完了,最后输出的时候要开方(前面为了精度你可以选择不开方留到最后再开),注意输出保留 \(4\) 位小数。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 2e5+5;
const LL INF = 3e18+5;
struct node{LL x,y;}a[N],tmp[N];
int n;
LL read(){
    LL su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
bool cmp(node d1,node d2){return d1.x<d2.x;}
LL e2(LL x){return x*x;}
LL solve(int l,int r){
	if(l==r)return INF;
	int mid=(l+r-1)>>1;
	LL midx=a[mid].x;
	LL dis=min(solve(l,mid),solve(mid+1,r));
	int p1=l,p2=mid+1;
	for(int i=l;i<=r;i++)
	    if((p2>r)||(p1<=mid&&a[p1].y<a[p2].y))tmp[i]=a[p1++];
	    else tmp[i]=a[p2++];
	for(int i=l;i<=r;i++)a[i]=tmp[i];
	int cnt=0;
	for(int i=l;i<=r;i++)
	    if(e2(a[i].x-midx)<=dis)tmp[++cnt]=a[i];
	for(int i=1;i<=cnt;i++)
	    for(int j=i-1;j>=1&&e2(tmp[i].y-tmp[j].y)<=dis;j--)
	        dis=min(dis,e2(tmp[i].x-tmp[j].x)+e2(tmp[i].y-tmp[j].y));
    return dis;
}
int main(){
	n=read();
	for(int i=1;i<=n;i++)
		a[i].x=read(),a[i].y=read();
	sort(a+1,a+n+1,cmp);
	LD ans=sqrt(solve(1,n));
	printf("%.4Lf\n",ans);
    return 0;
}

总结

分治好吃。

我发现就算不要求自己写那么好,我也会在很认真的写啊,我真的,为什么我会这么喜欢写题解,谁能给我一个解释啊。

分治好吃。

为什么明天还是背包啊?

后天的 KMP 不会怎么办??

posted @ 2026-07-21 21:10  嘎嘎喵  阅读(29)  评论(0)    收藏  举报