[29] CSP模拟2

A.不相邻集合

考虑值域上连续的段,可以发现连续的长度为 x 的段的贡献必定为 x2

考虑并查集维护值域连续的段的大小,每次询问求出全部连续段的 size2 之和即为答案

合并操作:在值域上加入 x,尝试合并 x1x+1

复杂度不对,考虑优化

  1. 记一个关于值域的 vis,若单次插入不改变 vis,则答案不变
  2. 每次在并查集上合并时直接统计答案,先减去两个分别的答案,再加上总和的答案
  3. 记得新加入单点时的贡献为 12=1
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define void inline void
// #define ONLINE_JUDGE
#ifndef ONLINE_JUDGE
#define test(i) cout<<"test: "<<i<<endl
#define testp(i,j) cout<<i<<" "<<j<<endl
#define testd(i) cout<<i<<" "
#define end cout<<"\n"
#define div <<" "<<
#else
#define test(i)
#define testp(i,j)
#define testd(i)
#define end false
#define div ,
#endif
template<typename T>
void read(T& x){
	x=0;bool sym=0;char c=getchar();
    while(!isdigit(c)){sym^=(c=='-');c=getchar();}
    while(isdigit(c)){x=x*10+c-48;c=getchar();}
    if(sym)x=-x;
}
template<typename T,typename... Args>
void read(T& x,Args&... args){
    read(x);read(args...);
}
vector<int>has;
int ans=0;
class dsu{
	public:
	int fa[500001],siz[500001];
	void reset(int n){
		for(int i=1;i<=n;++i){
			fa[i]=i;
			siz[i]=1;
		}
	}
	int find(int id){
		if(fa[id]==id) return id;
		fa[id]=find(fa[id]);
		return fa[id];
	}
	void connect(int x,int y){
		int fx=find(x),fy=find(y);
		if(fx!=fy){
			fa[fx]=fy;
			ans-=(siz[fy]+1)/2+(siz[fx]+1)/2;
			siz[fy]+=siz[fx];
			ans+=(siz[fy]+1)/2;
		}
	}
};
dsu d;
int a[300001];
bool vis[500001];
int tot,lastans=0;
int main(){
	int n;
	read(n);d.reset(500000);
	for(int i=1;i<=n;++i){
		read(a[i]);
		if(!vis[a[i]]){
			vis[a[i]]=true;
			has.push_back(a[i]);
			ans++;
			if(vis[a[i]-1]){
				d.connect(a[i],a[i]-1);
			}
			if(vis[a[i]+1]){
				d.connect(a[i],a[i]+1);
			}
		}
		cout<<ans<<' ';
	}
}

B.线段树

f(n,x) 表示对从长度为 n 的连续段建立线段树(即有 n 个叶节点),根节点的值为 x,左儿子的值为 2x,右儿子的值为 2x+1 的情况下的全部节点权值和

可以发现对于根节点儿子的权值,相对于根节点权值的变化只有两种:即乘以一个系数或者加上若干值,也即 f(n,x) 是关于 x 的一次函数

f(n,x)=knx+bn,考虑到我们在对此线段树向下分治的时候,总会有 n2 长度的区间被分配到左儿子,n2 长度的区间被分配到右儿子,也就是说:

f(n,x)=f(n2,2x)+f(n2,2x+1)+x

根据上设,可得

knx+bn=kn22x+bn2+kn2(2x+1)+bn2+x

解这个方程,得到

{kn=2kn2+2kn2+1bn=bn2+kn2+bn2

关于这个递推式的初态,可以感性理解

  • n=1 时,线段树中只有一个节点 x,因此 k1=1,b1=0
  • n=2 时,线段树中只有三个节点 x,2x,2x+1,和为 5x+1,因此 k1=5,b1=1

其余开 map 记搜即可

如果你们 WA60 也有可能是 build() 里的 id 忘记取模了

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
template<typename T> 
inline T read(){
	T x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch-'0');
		ch=getchar();
	}
	return x*f;
}
template<typename T> 
inline T read(T&a){
	T x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch-'0');
		ch=getchar();
	}
	return a=x*f;
}
template<typename T>
inline void write(T x){
	if(x<0)x*=-1,putchar('-');
	if(x>9)write(x/10);
	putchar(x%10+'0');
	return;
}
const int p=1e9+7;
map<int,int> K,B;
int k(int n){
	if(n==0) return 0;
	if(n==1) return 1;
	if(n==2) return 5;
	if(K.count(n)) return K[n];
	return K[n]=(2*(k((n+1)/2)+k(n/2))+1)%p;
}
int b(int n){
	if(n==0) return 0;
	if(n==1) return 0;
	if(n==2) return 1;
	if(B.count(n)) return B[n];
	return B[n]=(b((n+1)/2)+b(n/2)+k(n/2))%p;
}
int build(int id,int l,int r,int L,int R){
	if(l>R or r<L) return 0;
	if(L<=l and r<=R){
		return (k(r-l+1)*id%p+b(r-l+1))%p;
	}
    int mid=(l+r)/2;
    return (build((id*2)%p,l,mid,L,R)+build((id*2+1)%p,mid+1,r,L,R))%p;
}
signed main(){
    int T;read(T);
    while(T--){
        int n,l,r;
        read(n);read(l);read(r);
        write(build(1,1,n,l,r)%p);putchar('\n');
    }
}

C.魔法师

题目需要我们求 max(ai+aj,bi+bj)

分类讨论,注意到 ai+aj>bi+bj 的时候,只需要令 aibi>bjaj 即可,此时的答案即为 ai+aj,否则,当 aibibjaj 时,答案便为 bi+bj,这样我们就去除了 max 符号

因此我们尝试对每个 ui=aibi,vi=biai 进行比较. 现在我们可以把问题转化为求满足 ui>vj(i,j)ai+aj 最小值,或者满足 uivj(i,j)bi+bj 最小值,在两者中再取一个最小值即为答案.

考虑对 u,v 建一颗权值线段树,考虑到,当 i 在左子树,j 在右子树时,一定有 uivj,因此 (i,j) 可以用于更新父亲节点 bi+bj 的最小值,反之同理,而我们需要的整体最小值,要么就是从子节点继承上来的答案,要么就是该节点的 i 在左子树,j 在右子树时的情况或者 j 在左子树,i 在右子树时贡献的情况,可以发现这样做正好覆盖了全部合法的 (i,j)

因为在 (i,j) 中,一定有前者为法杖,后者为咒语。因此在增加操作中,遇到法杖 i 则在 ab 位置插入节点 (ai,bi),遇到咒语 j 则在 ba 位置插入节点 (aj,bj),然后按顺序向上更新每层法杖的 a,b 最小值,咒语的 a,b 最小值,然后在当前节点用左子树法杖,右子树咒语,或者左子树咒语,右子树法杖的最小值分别计算贡献并取最小值即可

关于删除操作,注意到同一个叶节点下方可能会有多个 (a,b),因此考虑建树套树,简化一点开个 set 维护,因为我们需要分别统计四个最值,因此对每个叶节点开四个 set,分别以不同关键字排序,统计答案的时候取首位维护最小值,删除的时候直接暴力搜索删除,记得删除之后要将该节点统计信息清空后重新统计一遍,防止原先较大的答案未被覆盖导致的错误.

处于空间考虑,我们只需要对每个叶节点开一个 set,因此考虑将 set 建在外面,线段树里面只存储 set 的编号来处理. 由于 ab 可能存在负数,因此需要整体偏移,鉴于 a,b[0,2.5e5],偏移量为 02.5e5=2.5e5,线段树范围为 2.5e50(2.5e5)=5e5,最大值不超过 2.5e5+2.5e5=5e5,不用开 long long

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
template<typename T>
void read(T& x){
	x=0;bool sym=0;char c=getchar();
    while(!isdigit(c)){sym^=(c=='-');c=getchar();}
    while(isdigit(c)){x=x*10+c-48;c=getchar();}
    if(sym)x=-x;
}
template<typename T,typename... Args>
void read(T& x,Args&... args){
    read(x);read(args...);
}
#define tests int cases;read(cases);while(cases--)
#define pb push_back
int Q,T;
const int N=5e5+10,dx=N/2;
const int inf=1e8;
int lastans=0;
struct node{
	int a,b;
	bool operator <(const node &A)const{
		if(a==A.a) return b<A.b;
		return a<A.a;
	}
};
struct node2{
	int a,b;
	bool operator <(const node2 &A)const{
		if(b==A.b) return a<A.a;
		return b<A.b;
	}
};
struct setpair{
	set<node> pa,qa;
	set<node2> pb,qb;
};
vector<setpair>store;
namespace Stree{
	#define tol (id*2)
	#define tor (id*2+1)
	#define mid(l,r) mid=(l+r)/2
	struct tree{
		int l,r;
		int store_id;
		int minap,minaq,minbp,minbq;
		int minansa,minansb;
	}t[4*N];
	void pushup(int id){
		t[id].minap=inf;
		t[id].minaq=inf;
		t[id].minbp=inf;
		t[id].minbq=inf;
		t[id].minansa=t[id].minansb=inf;
		if(t[tol].l){
			t[id].minap=min(t[tol].minap,t[id].minap);
			t[id].minaq=min(t[tol].minaq,t[id].minaq);
			t[id].minbp=min(t[tol].minbp,t[id].minbp);
			t[id].minbq=min(t[tol].minbq,t[id].minbq);
			t[id].minansa=min(t[tol].minansa,t[id].minansa);
			t[id].minansb=min(t[tol].minansb,t[id].minansb);
		}
		if(t[tor].l){
			t[id].minap=min(t[tor].minap,t[id].minap);
			t[id].minaq=min(t[tor].minaq,t[id].minaq);
			t[id].minbp=min(t[tor].minbp,t[id].minbp);
			t[id].minbq=min(t[tor].minbq,t[id].minbq);
			t[id].minansa=min(t[tor].minansa,t[id].minansa);
			t[id].minansb=min(t[tor].minansb,t[id].minansb);
		}
		if(t[tol].l and t[tor].l){
			t[id].minansa=min({t[tol].minansa,t[tor].minansa,t[tol].minaq+t[tor].minap});
			t[id].minansb=min({t[tol].minansb,t[tor].minansb,t[tol].minbp+t[tor].minbq});
		}
	}
	void build(int id,int l,int r){
		t[id].l=l;t[id].r=r;
		t[id].minap=inf,t[id].minaq=inf;
		t[id].minbp=inf,t[id].minbq=inf;
		t[id].minansa=inf;t[id].minansb=inf;
		if(l==r){
			store.push_back({});
			t[id].store_id=(int)store.size()-1;
			return;
		}
		int mid(l,r);
		build(tol,l,mid);
		build(tor,mid+1,r);
	}
	void update(int id,bool isp){
		if(!store[t[id].store_id].pa.empty()) t[id].minap=store[t[id].store_id].pa.begin()->a;
		else t[id].minap=inf;
		if(!store[t[id].store_id].pb.empty()) t[id].minbp=store[t[id].store_id].pb.begin()->b; 
		else t[id].minbp=inf;
		if(!store[t[id].store_id].qa.empty()) t[id].minaq=store[t[id].store_id].qa.begin()->a;
		else t[id].minaq=inf;
		if(!store[t[id].store_id].qb.empty()) t[id].minbq=store[t[id].store_id].qb.begin()->b;
		else t[id].minbq=inf;
		t[id].minansa=min(inf,t[id].minap+t[id].minaq);
		t[id].minansb=min(inf,t[id].minbp+t[id].minbq);
	}
	void update2(int id,bool isp,int a,int b){
		if(isp){
			t[id].minap=min(t[id].minap,a);
			t[id].minbp=min(t[id].minbp,b);
		}
		else{
			t[id].minaq=min(t[id].minaq,a);
			t[id].minbq=min(t[id].minbq,b);
		}
		t[id].minansa=min(inf,t[id].minap+t[id].minaq);
		t[id].minansb=min(inf,t[id].minbp+t[id].minbq);
	}
	void change(int id,int pos,bool isp,int a,int b){
		if(t[id].l==t[id].r){
			if(isp){
				store[t[id].store_id].pa.insert({a,b});
				store[t[id].store_id].pb.insert({a,b});
			}
			else{
				store[t[id].store_id].qa.insert({a,b});
				store[t[id].store_id].qb.insert({a,b});
			}
			update2(id,isp,a,b);
			return;
		}
		if(pos<=t[tol].r) change(tol,pos,isp,a,b);
		else change(tor,pos,isp,a,b);
		pushup(id);
	}
	void remove(int id,int pos,int isp,int a,int b){
		if(t[id].l==t[id].r){
			if(isp){
				auto it1=store[t[id].store_id].pa.lower_bound(node{a,b});
				auto it2=store[t[id].store_id].pb.lower_bound(node2{a,b});
				if(it1!=store[t[id].store_id].pa.end() and it1->a==a and it1->b==b){
					store[t[id].store_id].pa.erase(it1);
					store[t[id].store_id].pb.erase(it2);
				}
			}
			else{
				auto it1=store[t[id].store_id].qa.lower_bound(node{a,b});
				auto it2=store[t[id].store_id].qb.lower_bound(node2{a,b});
				if(it1!=store[t[id].store_id].qa.end() and it1->a==a and it1->b==b){
					store[t[id].store_id].qa.erase(it1);
					store[t[id].store_id].qb.erase(it2);
				}
			}
			update(id,isp);
			return;
		}
		if(pos<=t[tol].r) remove(tol,pos,isp,a,b);
		else remove(tor,pos,isp,a,b);
		pushup(id);
	}
	int solve(){
		return min(t[1].minansa,t[1].minansb);
	}
}
using namespace Stree;
signed main(){
	read(Q,T);
	build(1,1,N);
	for(int i=1;i<=Q;++i){
		int op,t,a,b;read(op,t,a,b);
		if(T==1){
			a^=lastans;b^=lastans;
		}
		if(op==1){
			if(t==0){
				change(1,a-b+dx,true,a,b);
			}
			else{
				change(1,b-a+dx,false,a,b);
			}
		}
		else{
			if(t==0){
				remove(1,a-b+dx,true,a,b);
			}
			else{
				remove(1,b-a+dx,false,a,b);
			}
		}
		int res=solve();
		cout<<(lastans=(res==inf?0:res))<<endl;
	}
}


posted @   HaneDaniko  阅读(88)  评论(4编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示