分治

这是一些可以让你身败名裂的东西。

点分治

有人已经发现了这个恐怖的事实,事实就是我现在看半年前的点分治代码根本看不懂。
好吧,事实上今天上午也没讲点分治。

cdq分治

这个东西吧,我觉得吧,建议别学,过于困难。其实寒假学过,但是没有一个字是自己写的。

前置知识之求逆序对个数

给定 \(n\) 个数,每个数都有一个权值 \(a_i\),求满足 \(i\le j\) 同时 \(a_i\geq a_j\) 的点对个数。

显然,我们只需按顺序遍历一遍,用树状数组或者线段树维护一下在当前点之前,大于等于当前 \(a_i\) 的点个数即可。
至于它为什么对吧,显然你按顺序遍历就一定满足 \(i\neq j\),同时你维护的就是 \(a_j \geq a_i\),所以正确性十分显然啊。

三维偏序

给定 \(n\) 个数,每个数有3个值 \(u_i,v_i,w_i\),求满足 \(u_i\le u_j,v_i\le v_j,w_i\le w_j\)的点对个数。

假如令 \(w_i=1\),那么答案就很显然了,我们只需要把它当成逆序对来算就可以。
同时,我们灵光一现,发现还有一个东西叫做归并排序,同时,归并排序前对 \(u\) 排序,就一定可以保证左半边的 \(u_i\) 值是小于等于右半边的,同时,归并排序本身就可以维护使 \(v\) 有序,因此,我们只需要再用树状数组维护一下 \(w\) 的顺序即可。
所以 cdq分治的思路就很显然了,分三层维护其有序性,首先在外面对 \(u\) 排个序,然后再使用归并排序,通过合并维护 \(v\) 的顺序,最后在归并排序过程中,我们考虑用个什么东西维护最后一维就好了。

#include<bits/stdc++.h>
#define int long long 
#define lowbit(x) (x&(-x))
using namespace std;
int n,k,ans=0;
const int N=2e5+100;
int c[N],sum[N];
struct node {
	int x,y,z,ans,cnt;
	friend bool operator <(node a,node b) {
		if(a.x!=b.x) return a.x<b.x;
		if(a.y!=b.y) return a.y<b.y;
		return a.z<b.z;
	}
}a[N],b[N];
map<node,int>mp;
int read(){int x;cin>>x;return x;}
void insert(int x,int d){
	if(x==0) return ;
	while(x<=k){
		c[x]+=d;
		x+=lowbit(x);
	}
}
int ask(int x){
	int t=0;
	while(x){
		t+=c[x];
		// if(c[x]<0) cerr<<"GGGGG "<<x<<endl;
		x-=lowbit(x);
	}	
	return t;
}
void merge(int l,int r) {
	if(l==r) return ;
	int mid=(l+r)>>1;
	merge(l,mid);merge(mid+1,r);
	int i=l,j=mid+1,now=l;
	for(;i<=mid&&j<=r;now++) {
		if(a[i].y<=a[j].y) {
			b[now]=a[i];
			insert(a[i].z,a[i].cnt);
			i++;
		}
		else {
			b[now]=a[j];
			b[now].ans+=ask(a[j].z);
			j++;
		}
	} 
	for(;i<=mid;) {b[now]=a[i];insert(a[i].z,a[i].cnt);i++,now++;}
	for(;j<=r;j++,now++) {b[now]=a[j];b[now].ans+=ask(a[j].z);
	}
	for(int i=l;i<=mid;i++) {insert(a[i].z,-a[i].cnt);}
	for(int i=l;i<=r;i++) a[i]=b[i];
	return ;
}
signed main() {
	freopen("flower.in","r",stdin);
	freopen("flower.out","w",stdout);
	memset(c,0,sizeof(c));
	n=read();k=read();
	int x,y,z;
	for(int i=1;i<=n;i++) {
		x=read();y=read();z=read();
		mp[{x,y,z,0,0}]++;
	}
	int cnt=0;
	for(auto y:mp) {
		cnt++;
		node t=y.first;
		t.cnt=y.second;
		a[cnt]=t;
		a[cnt].ans=0;
	}
	sort(a+1,a+1+cnt);
	merge(1,cnt);
	for(int i=1;i<=cnt;i++) {
		a[i].ans=a[i].ans+a[i].cnt-1ll;
		sum[a[i].ans]+=a[i].cnt;
	}
	for(int i=0;i<n;i++) {
		cout<<sum[i]<<"\n";
	}
	return 0;
}

导弹拦截

对于第一问,其实就是在问对于 \(i\le j\) 满足 \(a_i\le a_j,b_i\le b_j,c_i\le c_j\) 的最长子序列,我会cdq,每次我们先处理出来左区间的,然后从左区间往右区间转移,转移完了之后,我们让右区间自己转移转移,本质上的转移顺序和暴力是一样的。那么,你就获得了40pts的高分。
接下来,对于第二问,你考虑怎么办,显然,在转移的同时,维护一个方案数是可行的,这样你就处理出来了一共有多少种最长上升子序列。然后考虑哪些点可以出现在这个最长上升子序列里面,实际上就是你再反着做一遍,设 \(f_i\) 表示以 \(i\) 为结尾的最长子序列,设 \(g_i\) 表示以 \(i\) 为开头的最长上升子序列,那么,当且仅当 \(g_i+f_i-1==maxx\) 时,这个点是会出现在最长上升子序列里面的。然后我们就可以同时维护一下这两个的方案数,最后一除,然后就做完了。

#include<bits/stdc++.h>
#define int long long 
#define lowbit(x) ((x)&(-x))
using namespace std;
int n;
const int N=1e5+100;
double anss[N];
pair<int,double> c[N];
struct node {
    int h,v,id;
    int f=1,g=1;
    double p1=1.0,p2=1.0;
} st[N];
vector<int> o;
int read() {int x;cin>>x;return x;} 
bool cmp1(node a,node b) {
    if(a.h!=b.h) return a.h>b.h;
    return a.id<b.id;
}
bool cmp3(node a,node b) {
    if(a.h!=b.h) return a.h<b.h;
    return a.id<b.id;
}
bool cmp2(node a,node b) {
    return a.id<b.id;
}
void add(int x,pair<int,double> y) {
    if(x<=0) return;
    while(x<=n+5) {
        if(c[x].first<y.first) 
            c[x]=y;
        else if(c[x].first==y.first) 
            c[x].second+=y.second;
        x+=lowbit(x);
    }
}
pair<int,double> ask(int x) {
    pair<int,double> t={0,0};
    while(x>0) {
        if(t.first<c[x].first) 
            t=c[x];
        else if(t.first==c[x].first) 
            t.second+=c[x].second;
        x-=lowbit(x);
    }
    return t;
}
void add1(int x,pair<int,double> y) {
    if(x<=0) return;
    while(x>0) {
        if(c[x].first<y.first) 
            c[x]=y;
        else if(c[x].first==y.first) 
            c[x].second+=y.second;
        x-=lowbit(x);
    }
}

pair<int,double> ask1(int x) {
    pair<int,double> t={0,0};
    while(x<=n+5) {
        if(t.first<c[x].first) 
            t=c[x];
        else if(t.first==c[x].first) 
            t.second+=c[x].second;
        x+=lowbit(x);
    }
    return t;
}
void clear(int x) {
    while(x<=n+5) {
        c[x]=make_pair(0,0.0);
        x+=lowbit(x);
    }
}
void clear1(int x) {
    while(x>0) {
        c[x]=make_pair(0,0.0);
        x-=lowbit(x);
    }
}
void work(int l,int r) {
    if(l==r) return;
    int mid=(l+r)>>1;
    work(l,mid);
    vector<node> left_arr, right_arr;
    for(int i=l;i<=mid;i++) left_arr.push_back(st[i]);
    for(int i=mid+1;i<=r;i++) right_arr.push_back(st[i]);
    
    sort(left_arr.begin(),left_arr.end(),cmp1);
    sort(right_arr.begin(),right_arr.end(),cmp1);
    int i=0,j=0;
    while(i<left_arr.size()||j<right_arr.size()) {
        if(i<left_arr.size()&&(j>=right_arr.size()||left_arr[i].h>=right_arr[j].h)) {
            add1(left_arr[i].v,make_pair(left_arr[i].f,left_arr[i].p1));
            i++;
        } else {
            pair<int,double> res=ask1(right_arr[j].v);
            if(res.first+1>right_arr[j].f) {
                right_arr[j].f=res.first+1;
                right_arr[j].p1=res.second;
            } else if(res.first+1==right_arr[j].f) {
                right_arr[j].p1+=res.second;
            }
            j++;
        }
    }
    for(int i=0;i<left_arr.size();i++) clear1(left_arr[i].v);
    for(int i=mid+1;i<=r;i++) {
        for(int j=0;j<right_arr.size();j++) {
            if(st[i].id==right_arr[j].id) {
                st[i]=right_arr[j];
                break;
            }
        }
    }
    work(mid+1,r);
}
void cdq(int l,int r) {
    if(l==r) return;
    int mid=(l+r)>>1;
    cdq(l,mid);
    vector<node> left_arr, right_arr;
    for(int i=l;i<=mid;i++) left_arr.push_back(st[i]);
    for(int i=mid+1;i<=r;i++) right_arr.push_back(st[i]);
    sort(left_arr.begin(),left_arr.end(),cmp3);
    sort(right_arr.begin(),right_arr.end(),cmp3);
    int i=0,j=0;
    while(i<left_arr.size()||j<right_arr.size()) {
        if(i<left_arr.size()&&(j>=right_arr.size()||left_arr[i].h<=right_arr[j].h)) {
            add(left_arr[i].v,make_pair(left_arr[i].g,left_arr[i].p2));
            i++;
        } else {
            pair<int,double> res=ask(right_arr[j].v);
            if(res.first+1>right_arr[j].g) {
                right_arr[j].g=res.first+1;
                right_arr[j].p2=res.second;
            }
			else if(res.first+1==right_arr[j].g) 
                right_arr[j].p2+=res.second;
            j++;
        }
    }
    for(int i=0;i<left_arr.size();i++) clear(left_arr[i].v);
    for(int i=mid+1;i<=r;i++) {
        for(int j=0;j<right_arr.size();j++) {
            if(st[i].id==right_arr[j].id) {
                st[i]=right_arr[j];
                break;
            }
        }
    }
    cdq(mid+1,r);
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    n=read();
    for(int i=1;i<=n;i++) {
        st[i].h=read();
        st[i].v=read();
        st[i].id=i;
        o.push_back(st[i].v);
    }
    sort(o.begin(),o.end());
    o.erase(unique(o.begin(),o.end()),o.end());
    for(int i=1;i<=n;i++) 
        st[i].v=lower_bound(o.begin(),o.end(),st[i].v)-o.begin()+1;
    work(1,n);
    for(int i=1;i<=n/2;i++) swap(st[i],st[n-i+1]);
    for(int i=1;i<=n;i++) {
        st[i].g=1;
        st[i].p2=1.0;
    }
    cdq(1,n);
    for(int i=1;i<=n/2;i++) swap(st[i],st[n-i+1]);
    int ans=0;
    double cnt=0;
    for(int i=1;i<=n;i++) ans=max(ans,st[i].f);
    for(int i=1;i<=n;i++) 
        if(st[i].f==ans) cnt+=st[i].p1;
    cout<<ans<<endl;
    for(int i=1;i<=n;i++) {
        if(st[i].f+st[i].g-1==ans) anss[st[i].id]=(st[i].p1*st[i].p2)/cnt; 
		else anss[st[i].id]=0.0;
    }
    for(int i=1;i<=n;i++) printf("%.5lf ",anss[i]);
    return 0;
}

线段树分治

容易注意到,三个月之前我开过这一篇总结,但是那个摘要里面的只有标题至今都还是只有标题。

分治

就是,你考虑,对于一些求什么什么区间的问题,我们就把这个问题递归下去做,然后做一做左区间的,做一做右区间的,然后合并一下,就做完了。

消失之物

容易想到,这题可以退背包来写,但是我们今天学分治,所以我们考虑分治,你考虑你每次往下走的时候,都把另一个区间的点给加进来,然后你走到最下面叶子的时候,一定就只有这个点还没有被加进来,然后直接统计答案就好。
然后考虑时间复杂度,显然,每个点最多会被加入 \(\log n\) 次,所以总时间复杂度 \(O(nmlogn)\) 可以接受。

ARC_070_D No Need

这题和上一题本质上是一样的啊,就是你把方案数转换成可达性,然后,你去分治,这个样子的话,你就不会炸int了,就是显然,这题你不能退背包,因为你会炸。
当然,如果你是一个头铁的人,那么,容易发现,你只需要和写哈希的时候一样,取成双模数就好,一半没人闲得慌卡你。
这里插播一句,曾经啊,有一场abc,我们发现,E是哈希板子,这还不是手到擒来,于是作者和Air就开始大力码hash,由于作者双哈希没封装,被Air吐槽马蜂丑陋,结果交上去发现只有作者能过,不知为何封装就是过不去,然后Air就炸了,最后被迫写的作者马蜂丑陋的那一版。

最大公约数

你考虑,这题有一个性质啊,你左端点固定,右端点越远,是不是gcd就越小啊,于是考虑gcd的突变点,你考虑它每次突变,起码是要/2 的,所以,容易发现,最多会有 \(\log(n)\) 个突变点,所以,你怎么处理出来那个突变点,然后做一做就好了。
不过吧,这一种做法,代码非常之难写,所以我们考虑换一个思路,我们直接分治,然后这个时候你直接暴力扫左右区间的突变点,接着你 \(O(log^2(n)\) 地跑一跑,然后这不就做完了吗。

posted @ 2025-10-09 13:58  wjx_2010  阅读(20)  评论(1)    收藏  举报