P3795 总统选举 题解
P3795 总统选举 题解
题目链接
题目大意
有 \(n\) 个人,每个人都要投一票。有 \(m\) 轮操作,每次求在区间 \([l_i,r_i]\) 里面有没有人被投票数超过人数的一半,输出这个数,并使 \(k_i\) 个人把自己的票投给获胜者。如果不存在人满足条件,则让 \(c_i\) 获胜。最后输出整个区间内超过区间一半的人。没有输出 \(-1\)。
前置知识
线段树,平衡树,(随机化),摩尔投票法
摩尔投票法
给定一个可重集合,求这个序列的众数。保证这个众数的数量大于集合大小的一半。
我们考虑这个限制条件,发现数量大于集合大小的一半的数有且只有一个。
考虑存在一个小黑屋,其中我们把众数放进去,然后让集合里面的其他数一一去和他抵消。如果这个众数的大小大于集合的一半,我们发现其一定无法抵消掉里面所有数,一定会有数剩下。
考虑依次加数,显然最后剩下的一定是众数。
cin>>n;
for(int i=1;i<=n;i++)
{
int x;
cin>>x;
if(now==x) cnt++;
if(cnt==0) now=x,cnt++;
if(x!=now) cnt--;
}
cout<<now<<endl;
考虑分析时间复杂度,空间复杂度,发现时间复杂度显然是 \(O(n)\) 的,而空间连数组都不用看,只需要 \(O(1)\) 的空间即可。
本题做法
找到可能的众数
线段树
发现众数修改,考虑维护众数。考虑摩尔投票的性质,发现其显然满足区间可加性,考虑线段树维护。
具体来说,考虑维护两个信息,一个是当前区间的众数 \(num\),一个是当前区间众数出现净次数 \(cnt\)(指摩尔排序后留下的众数个数)。
如果两个子区间众数相同,则直接把净次数相加即可,否则,大减小即可。
随机化
每次在区间中随机取 \(K\) 个数,然后判断这个数是不是区间众数。每次找不到小于众数的概率为 $\frac{1}{2} \(,\)k$ 次后还找不到众数的概率为 $\frac{1}{2^k} $(如果存在众数)。直接做即可。
判断是否是答案:平衡树
考虑通过线段树找到了答案,但是我们是通过摩尔投票找到的答案,而摩尔投票的前提是众数数大于当前集合大小一半。考虑判断该条件。显然,如果这个数不满足条件,即不满足摩尔投票的条件,肯定也不满足题目中的条件。
考虑平衡树实现。我们可以对于每一个人开一棵平衡树,每个人的平衡树里面存支持他的人的编号。对于一个区间 \([l,r]\),我们可以找到编号为 \([1,r]\) 之间的人支持找到众数的数量,也可以找到编号为 \([1,l-1]\) 之间的人支持众数的数量。显然不存在相同的数不同的众数,其本质就可以转化为找排名。
CODE
线段树+平衡树(Splay)
我Splay动态开点开了10倍空间,因为有删除操作,理论上来说写一个空间回收空间可以开的小一点。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+100,INF=1e9;
int n,m;
int dt[N];
struct SegmentTree{
#define l(x) x*2
#define r(x) x*2+1
struct NODE{
int cnt,num;
}tr[N*4];
void pushup(int p)
{
if(tr[l(p)].num==tr[r(p)].num) tr[p].num=tr[l(p)].num,tr[p].cnt=tr[l(p)].cnt+tr[r(p)].cnt;
else
if(tr[l(p)].cnt>=tr[r(p)].cnt) tr[p].cnt=tr[l(p)].cnt-tr[r(p)].cnt,tr[p].num=tr[l(p)].num;
else tr[p].cnt=tr[r(p)].cnt-tr[l(p)].cnt,tr[p].num=tr[r(p)].num;
}
void build(int p,int l,int r)
{
if(l==r) return tr[p].cnt=1,tr[p].num=dt[l],void();
int mid=l+r>>1;
build(l(p),l,mid),build(r(p),mid+1,r);
pushup(p);
}
void change(int p,int l,int r,int x,int w)
{
if(l==x && r==x) return tr[p].num=w,tr[p].cnt=1,void();
int mid=l+r>>1;
if(x<=mid) change(l(p),l,mid,x,w);
if(x>mid) change(r(p),mid+1,r,x,w);
pushup(p);
}
NODE ask(int p,int l,int r,int x,int y)
{
if(l==x && r==y) return tr[p];
int mid=l+r>>1;
if(mid>=y) return ask(l(p),l,mid,x,y);
else if(mid<x) return ask(r(p),mid+1,r,x,y);
else
{
NODE a=ask(l(p),l,mid,x,mid),b=ask(r(p),mid+1,r,mid+1,y);
NODE c;
if(a.num==b.num) c.num=a.num,c.cnt=a.cnt+b.cnt;
else
if(a.cnt>=b.cnt) c.cnt=a.cnt-b.cnt,c.num=a.num;
else c.cnt=b.cnt-a.cnt,c.num=b.num;
return c;
}
}
}Tr_seg;
struct Splay{
struct NODE{
int s[2],p,v;
int size;
void init(int _p,int _v)
{
p=_p,v=_v;
size=1;
}
}tr[N*10];
int root[N],idx;
int L[N];
void pushup(int x)
{
tr[x].size=tr[tr[x].s[0]].size+tr[tr[x].s[1]].size+1;
}
void rotate(int x)
{
int y=tr[x].p,z=tr[y].p;
int k=tr[y].s[1]==x;
tr[z].s[tr[z].s[1]==y]=x,tr[x].p=z;
tr[y].s[k]=tr[x].s[k^1],tr[tr[x].s[k^1]].p=y;
tr[x].s[k^1]=y,tr[y].p=x;
pushup(y),pushup(x);
}
void splay(int x,int k,int b)
{
while(tr[x].p!=k)
{
int y=tr[x].p,z=tr[y].p;
if(z!=k)
if((tr[y].s[0]==x)^(tr[z].s[0]==y)) rotate(x);
else rotate(y);
rotate(x);
}
if(!k) root[b]=x;
}
int insert(int v,int b)
{
int u=root[b],p=0;
while(u) p=u,u=tr[u].s[v>tr[u].v];
u=++idx;
if(p) tr[p].s[v>tr[p].v]=u;
tr[u].init(p,v);
splay(u,0,b);
return u;
}
void find(int x,int b)
{
int u=root[b];
if(!u) return;
while(tr[u].s[x>tr[u].v] && tr[u].v!=x) u=tr[u].s[x>tr[u].v];
splay(u,0,b);
}
int next(int x,int f,int b)
{
find(x,b);
int u=root[b];
if((tr[u].v>x && f==1) || (tr[u].v<x && f==0)) return u;
u=tr[u].s[f];
while(tr[u].s[f^1]) u=tr[u].s[f^1];
return u;
}
int get(int v,int b)
{
find(v,b);
if(tr[root[b]].v!=v) v=tr[next(v,0,b)].v;
int u=root[b],ans=0;
while(true)
{
if(tr[u].v>v) u=tr[u].s[0];
else if(tr[u].v==v)
return ans+=tr[tr[u].s[0]].size,splay(u,0,b),ans;
else ans+=tr[tr[u].s[0]].size+1,u=tr[u].s[1];
}
}
void remove(int x,int b)
{
int L=next(x,0,b),R=next(x,1,b);
splay(L,0,b),splay(R,L,b);
tr[R].s[0]=0;
pushup(L),pushup(R);
}
}Tr_Splay;
signed main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
Tr_Splay.L[i]=Tr_Splay.insert(-INF,i),Tr_Splay.insert(INF,i);
for(int i=1;i<=n;i++)
cin>>dt[i],Tr_Splay.insert(i,dt[i]);
Tr_seg.build(1,1,n);
for(int i=1;i<=m;i++)
{
int l,r,s,q;
cin>>l>>r>>s>>q;
int x=Tr_seg.ask(1,1,n,l,r).num;
int sum=Tr_Splay.get(r,x)-Tr_Splay.get(l-1,x);
if(sum>(r-l+1)/2) s=x;
for(int j=1;j<=q;j++)
{
cin>>x;
Tr_seg.change(1,1,n,x,s);
Tr_Splay.remove(x,dt[x]);
dt[x]=s;
Tr_Splay.insert(x,dt[x]);
}
cout<<s<<endl;
}
int x=Tr_seg.ask(1,1,n,1,n).num;
int sum=Tr_Splay.get(n,x);
if(sum>n/2) cout<<x<<endl;
else cout<<-1<<endl;
return 0;
}
随机化+pb_ds
pb_ds好闪!五分钟写完了我今天调了一天的Splay!
#include<bits/stdc++.h>
#include<bits/extc++.h>
#define LL long long
using namespace __gnu_pbds;
using namespace std;
const int N=5e5+100;
int n,m;
int a[N];
tree<LL,null_type,less<LL>,rb_tree_tag,tree_order_statistics_node_update> S[N];
//平衡树
int p[N];
int calc(int x,int l,int r)
{
return S[x].order_of_key(r+1)-S[x].order_of_key(l);
}
int solve(int l,int r)
{
int len=r-l+1;
for(int i=1;i<=20;i++)
{
int x=rand()*rand()%len+l;
x=a[x];
if(calc(x,l,r)>len/2) return x;
}
return -1;
}
signed main()
{
srand((unsigned)time(0));
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i],S[a[i]].insert(i);
for(int i=1;i<=m;i++)
{
int l,r,s,k;
cin>>l>>r>>s>>k;
for(int j=1;j<=k;j++) cin>>p[j];
int ans=solve(l,r);
if(ans==-1) ans=s;
cout<<ans<<endl;
for(int j=1;j<=k;j++)
{
S[a[p[j]]].erase(p[j]);
a[p[j]]=ans;
S[ans].insert(p[j]);
}
}
cout<<solve(1,n)<<endl;
return 0;
}

浙公网安备 33010602011771号