[做题记录]线段树

省选阶段的线段树题。

CF765F Souvenirs

我们使用扫描线,先只考虑\(a_j > a_r\)的情况,我们考虑先找到离\(r\)最近的\(j\)
那么此时对\([1,j]\)取一个\(a_j - a_r\)\(min\)

然后我们思考如果\(i > j\)\(a_i - a_r\)能更新的话,则有\(a_i - a_r < a_j - a_r\),且有\(a_j - a_i > a_i - a_r\)

则可计算出\(a_r \leq a_i \leq \frac{a_r + a_j}{2}\)

然后我们发现其每次都减少一半值域。

我们直接找并线段树取min修改,找的过程需要主席树 \(O(nlog^2n)\)

CF765F Souvenirs
#include<bits/stdc++.h>
#define ll long long 
#define N 100005
#define LIM 1000000105

int n;

int a[N];

int head[N];

int ch[N * 40][2];

int T[N * 40];

#define ls(x) ch[x][0]
#define rs(x) ch[x][1]
#define mid ((1ll * l + 1ll * r) >> 1)

inline void up(int x){T[x] = std::max(T[ls(x)],T[rs(x)]);}

int cnt;

inline void add(int lasu,int &u,int l,int r,int to,int p){
	if(!u)u = ++cnt;
	if(l == r){
	T[u] = std::max(T[u],p);
	return ;
	}
	if(to <= mid)
	rs(u) = rs(lasu),add(ls(lasu),ls(u),l,mid,to,p);
	else
	ls(u) = ls(lasu),add(rs(lasu),rs(u),mid + 1,r,to,p);
	up(u);
//	std::cout<<lasu<<" "<<u<<" "<<l<<" "<<r<<" "<<to<<" "<<p<<" "<<T[u]<<std::endl;	
}

inline int find(int u,int l,int r,long double tl,long double tr){
	if(l > tr)return 0; 
	if(tl <= l && r <= tr)
	return T[u];
	int ans = 0;
	if(tl <= mid)
	ans = std::max(ans,find(ls(u),l,mid,tl,tr));
	if(tr > mid)
	ans = std::max(ans,find(rs(u),mid + 1,r,tl,tr));
	return ans;
}

int F[N << 2];

#define Ls(x) (x << 1)
#define Rs(x) (x << 1 | 1)

inline void cover(int u,int l,int r,int tl,int tr,int p){
//	std::cout<<u<<" "<<l<<" "<<r<<" "<<tl<<" "<<tr<<" "<<p<<std::endl; 
	if(tl <= l && r <= tr){
		F[u] = std::min(F[u],p);
		return ;
	}
	if(tl <= mid)
	cover(Ls(u),l,mid,tl,tr,p);
	if(tr > mid)
	cover(Rs(u),mid + 1,r,tl,tr,p);
} 

inline int fans(int u,int l,int r,int t){
//	std::cout<<u<<" "<<l<<" "<<r<<" "<<t<<" "<<F[u]<<std::endl;
	if(l == r)
	return F[u];
	if(t <= mid)
	return std::min(F[u],fans(Ls(u),l,mid,t));
	if(t > mid)
	return std::min(F[u],fans(Rs(u),mid + 1,r,t));	
}

inline void clear(){
	std::memset(head,0,sizeof(head));
	std::memset(ch,0,sizeof(ch));
	std::memset(T,0,sizeof(T));	
	std::memset(F,0x3f,sizeof(F));				
	cnt = 0;
}

int m;

std::vector< std::pair<int,int> >Q[N];

int f[N * 3];

inline void del(){
	for(int r = 2;r <= n;++r){
//		std::cout<<"! "<<r<<std::endl;
		long double las = a[r];
		int k = find(head[r - 1],1,LIM,las,LIM);
		int a_las = a[k];
//		std::cout<<"("<<a[r]<<" "<<LIM<<") -> "<<a_las<<std::endl;
		las = (a[r] + a_las) * 0.5;
		if(k != 0)
		cover(1,1,n,1,k,(a_las - a[r]));
		while((las - a[r]) >= 1){
		k = find(head[r - 1],1,LIM,a[r],las);
		a_las = a[k];
//		std::cout<<"("<<a[r]<<" "<<las<<") -> "<<a_las<<std::endl;
		if(k != 0)		
		cover(1,1,n,1,k,(a_las - a[r]));	
		las = (a[r] + a_las) * 0.5;
		}		
		for(int i = 0;i < Q[r].size();++i){
//		std::cout<<"Q( "<<Q[r][i].first<<" "<<fans(1,1,n,Q[r][i].first)<<std::endl;
		f[Q[r][i].second] = std::min(fans(1,1,n,Q[r][i].first),f[Q[r][i].second]);			
		}
	}
}

int main(){
	std::memset(f,0x3f,sizeof(f));	
	scanf("%d",&n);
	for(int i = 1;i <= n;++i)
	scanf("%d",&a[i]);
	scanf("%d",&m);	
	for(int i = 1;i <= m;++i){
		int l,r;
		scanf("%d%d",&l,&r);
		Q[r].push_back(std::make_pair(l,i));
	}
	clear();
	for(int i = 1;i <= n;++i){
		add(head[i - 1],head[i],1,LIM,a[i],i);
	}
	del();
	for(int i = 1;i <= n;++i)
	a[i] = LIM - a[i];
	clear();
	for(int i = 1;i <= n;++i){
		add(head[i - 1],head[i],1,LIM,a[i],i);
	}
	del();
	for(int i = 1;i <= m;++i)
	std::cout<<f[i]<<std::endl;	
}

CF280D k-Maximum Subsequence Sum

刚开始一直在想能不能\(dp\),并写作矩阵形式。

但是是错误的想法。

考虑这是一个费用流模型,我们把\(i\)\(i + 1\)连一条流量为\(1\),费用为\(a_i / 0\)的边,然后\(S\)\(l\)连一条流量\(k\)的边,然后取最大费用流。

但是无法直接流,那我们自然思考模拟费用流。

即提供反悔关系,我们每次把选中的最大字段和区间加入答案并取反即可。

代码太难写了,咕咕咕咕咕。

[Code+#1]Yazid 的新生舞会

捏马马的,自闭了兄弟。

考虑枚举最终众数\(x\),把\(a[i] = x\)\(val\)设为\(1\),否则为\(-1\),等同于区间和大于\(0\)的区间数量。

考虑\(1\)的个数只有\(O(n)\)个。

我们自然考虑对每个等差数列求答案。

那么有\(\sum_{x}^y T_i\)

考虑写\(G_i = \sum T_i\)

那么即\(G_i\)为三阶前缀和。

考虑单点修改,维护三阶前缀和即可。

image

[Code+#1]Yazid 的新生舞会
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF = 0x3f3f3f3f;
const LL mod = 1e9 + 7;
const int N = 500005;
// 修改差分 来维护前缀和的前缀和
// c1 为差分d c2为d*i  c3 为d*i*i
LL c1[N * 2], c2[N * 2], c3[N * 2];
LL sum(int x) {
    LL res = 0;
    for (int i = x; i > 0; i -= i & -i) {
        res += c1[i] * (x + 2) * (x + 1) - c2[i] * (2 * x + 3) + c3[i];
    }
    return res / 2;
}
void add(int x, LL d, int n) {
    for (int i = x; i <= n; i += i & -i) {
        c1[i] += d;
        c2[i] += d * x;
        c3[i] += d * x * x;
    }
}
int a[N];
vector<int> b[N];
int main() {
    int n;
    scanf("%d%*d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        b[a[i]].push_back(i);
    }
    const int wc = n + 1;  // 偏移量,把[-n,n] 平移到 [1,2n+1]
    LL ans = 0;
    for (int i = 0; i < n; i++) {
        b[i].push_back(n + 1);
        int last = 0;
        for (int j = 0; j < b[i].size(); j++) {
            int y = 2 * j - last + wc, x = 2 * j - (b[i][j] - 1) + wc;
            // 查询 sum([1,t-1] 的权值和), 其中t在[x,y]范围内,
            ans += sum(y - 1) - (x >= 3 ? sum(x - 2) : 0);
            // [x,y] 这些数的权值+1
            add(x, 1, 2 * n + 1);
            add(y + 1, -1, 2 * n + 1);
            last = b[i][j];
        }
        last = 0;
        for (int j = 0; j < b[i].size(); j++) {
            int y = 2 * j - last + wc, x = 2 * j - (b[i][j] - 1) + wc;
            add(x, -1, 2 * n + 1);
            add(y + 1, 1, 2 * n + 1);
            last = b[i][j];
        }
    }
    printf("%lld\n", ans);
    return 0;
}
posted @ 2021-12-10 14:29  fhq_treap  阅读(59)  评论(0编辑  收藏  举报