[29] CSP模拟2
A.不相邻集合
考虑值域上连续的段,可以发现连续的长度为 \(x\) 的段的贡献必定为 \(\lceil{\frac{x}{2}}\rceil\)
考虑并查集维护值域连续的段的大小,每次询问求出全部连续段的 \(\lceil{\frac{size}{2}}\rceil\) 之和即为答案
合并操作:在值域上加入 \(x\),尝试合并 \(x-1\) 与 \(x+1\)
复杂度不对,考虑优化
- 记一个关于值域的 \(vis\),若单次插入不改变 \(vis\),则答案不变
- 每次在并查集上合并时直接统计答案,先减去两个分别的答案,再加上总和的答案
- 记得新加入单点时的贡献为 \(\lceil{\frac{1}{2}}\rceil=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)=k_{n}x+b_{n}\),考虑到我们在对此线段树向下分治的时候,总会有 \(\lceil{\frac{n}{2}}\rceil\) 长度的区间被分配到左儿子,\(\lfloor{\frac{n}{2}}\rfloor\) 长度的区间被分配到右儿子,也就是说:
根据上设,可得
解这个方程,得到
关于这个递推式的初态,可以感性理解
- 当 \(n=1\) 时,线段树中只有一个节点 \(x\),因此 \(k_{1}=1,b_{1}=0\)
- 当 \(n=2\) 时,线段树中只有三个节点 \(x,2x,2x+1\),和为 \(5x+1\),因此 \(k_{1}=5,b_{1}=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(a_{i}+a_{j},b_{i}+b_{j})\)
分类讨论,注意到 \(a_{i}+a_{j}\gt b_{i}+b_{j}\) 的时候,只需要令 \(a_{i}-b_{i}\gt b_{j}-a_{j}\) 即可,此时的答案即为 \(a_{i}+a_{j}\),否则,当 \(a_{i}-b_{i}\le b_{j}-a_{j}\) 时,答案便为 \(b_{i}+b_{j}\),这样我们就去除了 \(\max\) 符号
因此我们尝试对每个 \(u_{i}=a_{i}-b_{i},v_{i}=b_{i}-a_{i}\) 进行比较. 现在我们可以把问题转化为求满足 \(u_{i}\gt v_{j}\) 的 \((i,j)\) 的 \(a_{i}+a_{j}\) 最小值,或者满足 \(u_{i}\le v_{j}\) 的 \((i,j)\) 的 \(b_{i}+b_{j}\) 最小值,在两者中再取一个最小值即为答案.
考虑对 \(u,v\) 建一颗权值线段树,考虑到,当 \(i\) 在左子树,\(j\) 在右子树时,一定有 \(u_{i}\le v_{j}\),因此 \((i,j)\) 可以用于更新父亲节点 \(b_{i}+b_{j}\) 的最小值,反之同理,而我们需要的整体最小值,要么就是从子节点继承上来的答案,要么就是该节点的 \(i\) 在左子树,\(j\) 在右子树时的情况或者 \(j\) 在左子树,\(i\) 在右子树时贡献的情况,可以发现这样做正好覆盖了全部合法的 \((i,j)\)
因为在 \((i,j)\) 中,一定有前者为法杖,后者为咒语。因此在增加操作中,遇到法杖 \(i\) 则在 \(a-b\) 位置插入节点 \((a_{i},b_{i})\),遇到咒语 \(j\) 则在 \(b-a\) 位置插入节点 \((a_{j},b_{j})\),然后按顺序向上更新每层法杖的 \(a,b\) 最小值,咒语的 \(a,b\) 最小值,然后在当前节点用左子树法杖,右子树咒语,或者左子树咒语,右子树法杖的最小值分别计算贡献并取最小值即可
关于删除操作,注意到同一个叶节点下方可能会有多个 \((a,b)\),因此考虑建树套树,简化一点开个 set 维护,因为我们需要分别统计四个最值,因此对每个叶节点开四个 set,分别以不同关键字排序,统计答案的时候取首位维护最小值,删除的时候直接暴力搜索删除,记得删除之后要将该节点统计信息清空后重新统计一遍,防止原先较大的答案未被覆盖导致的错误.
处于空间考虑,我们只需要对每个叶节点开一个 set,因此考虑将 set 建在外面,线段树里面只存储 set 的编号来处理. 由于 \(a-b\) 可能存在负数,因此需要整体偏移,鉴于 \(a,b\in[0,2.5e5]\),偏移量为 \(0-2.5e5=-2.5e5\),线段树范围为 \(2.5e5-0-(-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;
}
}