数据结构2
不正常团伙
有n个人站成一行,每个人有一个魅力值,相同魅力值的人会形成一个团伙,定义一个团伙正常当且仅当团伙人数为2,有m个询问:[l,r]中的人组成团伙后,处于不正常团伙的人的魅力值之和。
$n,m,a_{i}<=10^{5}$
题解
这道题用莫队很好去维护信息,开一个桶就好了。就是一些特判:当前转移后到达2个,转移前是2个。
#include<ctime> #include<cmath> #include<cstdio> #include<iostream> #include<algorithm> using namespace std; #define ll long long const int maxn=100005; int n,m,size; int nowl,nowr; int a[maxn],pos[maxn]; ll nowans,ans[maxn]; int cnt[maxn]; struct question{ int l,r,id; }q[maxn]; template<class T>inline void read(T &x){ x=0;int f=0;char ch=getchar(); while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();} while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} x = f ? -x : x ; } bool cmp(question a,question b){ if(pos[a.l]!=pos[b.l]) return pos[a.l]<pos[b.l]; return a.r<b.r; } void add(int x){ cnt[a[x]]++; nowans+=a[x]; if(cnt[a[x]]==2) nowans-=2*a[x]; else if(cnt[a[x]]==3) nowans+=2*a[x]; } void del(int x){ cnt[a[x]]--; nowans-=a[x]; if(cnt[a[x]]==2) nowans-=2*a[x]; else if(cnt[a[x]]==1) nowans+=2*a[x]; } int main(){ freopen("abnormal.in","r",stdin); freopen("abnormal.out","w",stdout); read(n);read(m); size=sqrt(n+0.5); for(int i=1;i<=n;i++){ read(a[i]); pos[i]=(i-1)/size+1; } for(int i=1;i<=m;i++){ read(q[i].l);read(q[i].r); q[i].id=i; } sort(q+1,q+m+1,cmp); nowl=1;nowr=0; for(int i=1;i<=m;i++){ while(nowr<q[i].r) add(++nowr); while(nowl>q[i].l) add(--nowl); while(nowr>q[i].r) del(nowr--); while(nowl<q[i].l) del(nowl++); ans[q[i].id]=nowans; } for(int i=1;i<=m;i++) printf("%lld\n",ans[i]); }
出现次数的东西也可以用线段树维护,离线将询问按照右端点排序,在左端点储存答案,
先求出每个位置前面和他相同的位置,考虑将i这个位置加入后的影响。
[pre[i]+1,i]这个区间a[i]只有一个,所以都加上a[i]。
[pre[pre[i]]+1,pre[i]]这个区间有两个a[i],所以减去a[i]。
[pre[pre[pre[i]]]+1,pre[pre[i]]]这个区间之前有两个,所以加上3*a[i]
对于剩下的前面区间就直接加上[I]即可。
所以区间修改,单点查询
寒假yyr day10 couple(码风很难受)
#include<bits/stdc++.h> using namespace std; #define ls rt<<1 #define rs rt<<1|1 #define mid ((tr[rt].l+tr[rt].r)>>1) const int maxn=1e5+5; const int maxm=1e5+2; int n,q; int a[maxn],pos[maxn],pre[maxn]; int used[maxn]; long long ans[maxm]; long long lazy[maxn<<2]; struct tree{ int l,r; }tr[maxn<<2]; struct qu{ int x,y,id; }que[maxm]; bool cmp(qu a,qu b){ return a.y<b.y; } template<class T>inline void read(T &x){ x=0;char ch=getchar(); while(!isdigit(ch)) {ch=getchar();} while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} } void build(int rt,int l,int r){ tr[rt].l=l;tr[rt].r=r; if(l==r)return ; build(ls,l,mid); build(rs,mid+1,r); } void push_down(int rt){ lazy[ls]+=lazy[rt]; lazy[rs]+=lazy[rt]; lazy[rt]=0; } void modify(int rt,int l,int r,int val){ if(l<=tr[rt].l&&tr[rt].r<=r){ lazy[rt]+=val; return ; } if(lazy[rt]) push_down(rt); if(l<=mid) modify(ls,l,r,val); if(mid<r) modify(rs,l,r,val); } long long query(int rt,int po){ if(tr[rt].l==tr[rt].r) return lazy[rt]; if(lazy[rt]) push_down(rt); if(po<=mid) return query(ls,po); else return query(rs,po); } int main(){ freopen("couple.in","r",stdin); freopen("couple.out","w",stdout); read(n);read(q); for(int i=1;i<=n;i++) read(a[i]); for(int i=1;i<=n;i++){//找前面第一个与它相同的 pre[i]=pos[a[i]]; pos[a[i]]=i; } //for(int i=1;i<=n;i++) printf("%d ",pre[i]); build(1,1,n); for(int i=1;i<=q;i++){ read(que[i].x); read(que[i].y); que[i].id=i; } sort(que+1,que+q+1,cmp); //for(int i=1;i<=q;i++) printf("%d %d ",que[i].x,que[i].y); int j=1; for(int i=1;i<=n;i++){//移动右端点 modify(1,pre[i]+1,i,a[i]);//此区间只有一个a[i] if(pre[i]){ modify(1,pre[pre[i]]+1,pre[i],-a[i]);//此区间a[i]数量变为2 } if(pre[pre[i]]){ modify(1,pre[pre[pre[i]]]+1,pre[pre[i]],3*a[i]);//此区间a[i]数量为3,之前该区间a[i]和为0,现不满足恰好两个,一共要加3个 } if(pre[pre[pre[i]]]){ modify(1,1,pre[pre[pre[i]]],a[i]);//此区间数量简单增加就好 } while(j<=q&&que[j].y==i){ ans[que[j].id]=query(1,que[j].x); j++; } if(j>q) break; } for(int i=1;i<=q;i++) printf("%I64d\n",ans[i]); } /* 5 5 4 3 3 4 1 1 1 1 4 1 2 1 5 2 3 */
不正常国家
给出一颗n个点的有根树,根为1。每个点有权值,每个点的答案就是他所管辖的子树中,经过他的路径上所有点的异或和 的最大值。
输出每个点的答案。
$n<=10^{5},a_{i}在int范围$
题解
暴力就是先求出每个点到根的前缀异或和o,再枚举两个点,他们对他们lca的贡献就是(o[x]^o[y]^a[lca]),因为是点权所以要把抵掉的a[lca]异或回来。$O(n^{2}logn)$
用欧拉序求lca可以降到$O(n^{2})$,考试忘了是哪种欧拉序了。
考虑能不能利用01trie弄出来。对于当前节点x,先将他的一颗子树的01trie建出来(对于o值),然后对于剩下的每个子树先查询再丢入01trie(查询是带入o[y]^a[x]),最后对于根节点要先丢值进去再查询(答案可能就是这个点的点权,特判或者这样搞都行)。
对于每个节点都重新建01trie显然不行,有些信息是可以共用的,所以这个问题就又变成了树上启发式合并。
对于每个节点,先把轻儿子的答案找出来,然后删除建出的01trie,接着把重儿子的答案找出来。这个时候重儿子的01trie是可以利用的,他就相当于刚才讲的第一颗子树,然后再按照刚才说的去用轻儿子子树查询和建树。
需要注意的是查询和建树不能写在一个函数,不然得到的答案就可能是同一个儿子所在子树。
当然删除也不能直接memset,dfs跑一遍01trie回收节点即可。
#include<queue> #include<cstdio> #include<iostream> #include<algorithm> using namespace std; const int maxn=100005; int n,cnt,head[maxn]; int a[maxn],o[maxn],size[maxn],son[maxn]; int ret,fa[maxn],ans[maxn]; int go[maxn*40][2]; queue<int> q; struct edge{ int x,y,next; }e[maxn<<1]; template<class T>inline void read(T &x){ x=0;int f=0;char ch=getchar(); while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();} while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} x = f ? -x : x ; } void add_edge(int x,int y){ e[++cnt]=(edge){x,y,head[x]}; head[x]=cnt; } void dfs(int x){ size[x]=1; for(int i=head[x];i;i=e[i].next){ int y=e[i].y; if(y==fa[x]) continue; o[y]=o[x]^a[y]; fa[y]=x; dfs(y); size[x]+=size[y]; if(size[y]>size[son[x]]) son[x]=y; } } int newnode(){ if(!q.empty()) {int x=q.front();q.pop();return x;} return ++cnt; } void make(int x){ int now=1; for(int i=1<<30;i;i>>=1){ bool c=x&i; if(!go[now][c]) go[now][c]=newnode(); now=go[now][c]; } } int find(int x){ int now=1,res=0; for(int i=1<<30;i;i>>=1){ bool c=x&i; if(go[now][c^1]) now=go[now][c^1],res+=i; else now=go[now][c]; } return res; } void query(int x,int rt){ for(int i=head[x];i;i=e[i].next){ int y=e[i].y; if(y==fa[x]) continue; query(y,rt); } ret=max(ret,find(o[x]^a[rt])); } void add(int x,int rt){ for(int i=head[x];i;i=e[i].next){ int y=e[i].y; if(y==fa[x]) continue; add(y,rt); } make(o[x]); } void clear(int now,int s){ if(!now) return ; if(s==-1) return ; clear(go[now][0],s-1); clear(go[now][1],s-1); go[now][0]=go[now][1]=0; if(now!=1) q.push(now); } void get_ans(int x){ if(!x) return ; for(int i=head[x];i;i=e[i].next){ int y=e[i].y; if(y==fa[x]||y==son[x]) continue; get_ans(y); clear(1,30); } get_ans(son[x]); ret=0; for(int i=head[x];i;i=e[i].next){ int y=e[i].y; if(y==fa[x]||y==son[x]) continue; query(y,x); add(y,x); } make(o[x]); ret=max(ret,find(o[x]^a[x])); ans[x]=ret; } int main(){ freopen("irregular.in","r",stdin); freopen("irregular.out","w",stdout); read(n); for(int i=1;i<=n;i++) read(a[i]); o[1]=a[1];//写不写都可以 for(int i=1;i<n;i++){ int x,y; read(x);read(y); add_edge(x,y);add_edge(y,x); } cnt=1; dfs(1); get_ans(1); for(int i=1;i<=n;i++) printf("%d\n",ans[i]); }
不正常序列
定义一个不正常数列:
$F_{1}=1$
$F_{I}=(a*M_{i}+b*i+c) mod (10^{9}+7) (i>1)$
其中$M_{i}$是指数列${F_{1},F_{2}...F{i-1}}$的中位数。
规定当有偶数项的时候,中位数为较小的那个。
求$sum\_{i=1}^{n} F_{i}$
题解
难点在于求中位数。
维护两个堆,大根堆和小根堆。
大根堆储存从小到大排名为[1,(n+1)/2]的值,剩下的在小根堆。两者的堆顶连接两个区间
那么大根堆的堆顶就是中位数。
记大根堆堆顶为x,经过计算得出现在插入的数为y。
当y<x时,说明他在前半部分,就加入大根堆;不然加入小根堆。
为了维护中位数一直在大根堆,所以要保持大根堆的个数为(n+1)/2,当大根堆元素多了的时候,就将堆顶丢到小根堆;小根堆元素多了,就丢到大根堆。
这样丢一定是对的,因为大根堆堆顶就是区间里面最大的,丢掉就是将右边界缩小一个。小根堆同理。
#include<ctime> #include<queue> #include<cstdio> #include<iostream> #include<algorithm> using namespace std; #define ll long long const int mod=1000000007; ll a,b,c,ret; int n,num1,num2; template<class T>inline void read(T &x){ x=0;int f=0;char ch=getchar(); while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();} while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} x = f ? -x : x ; } priority_queue<ll> q; priority_queue<ll,vector<ll>,greater<ll> > p; int main(){ freopen("unnormal.in","r",stdin); freopen("unnormal.out","w",stdout); read(a);read(b);read(c);read(n); ret=1;num1=1; q.push(1); for(int i=2;i<=n;i++){ ll x=q.top(); ll y=(a*x%mod+b*i%mod+c)%mod; ret=ret+y; if(y<x) q.push(y),num1++; else p.push(y),num2++; while(num1>(i+1)/2) {x=q.top();q.pop();p.push(x);num1--;num2++;} while(num2>i/2) {x=p.top();p.pop();q.push(x);num2--;num1++;} } printf("%lld",ret); }
求和不取模.......