您的位置:首页 > 其它

一个假莫队算法总结 (bzoj2021 小z的袜子 bzoj1878 HH的项链 bzoj2120 数颜色)

2018-01-24 10:24 531 查看
这里强烈推荐我学莫队的 美妙的 大米饼的blog:

https://www.cnblogs.com/Paul-Guderian/p/6933799.html

bzoj 2021 小z的袜子

http://www.lydsy.com/JudgeOnline/problem.php?id=2038

正如大米饼所言

若谈及入门,那么BZOJ2038的美妙袜子一题堪称顶尖。

求出在某一段区间种能拿到相同颜色的袜子的最大值

区间不太分散

修改比较少

不要求在线

那就

美妙的暴力——莫队算法

通过尽量减少左右区间跳的次数

通过旁边的区间快速地求出当前区间的值

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
struct node
{
long long l,r,id;
long long n,m;
} tr[51000];
long long s[51000],pos[51000],ans;//s:区间中第i个颜色的袜子有多少只  pos:分块用
int c[51000];//color
int n,m;

long long sqr(int x){return x*x;}
int cmp(node x,node y)//排序 如果左端点在不在同一个块中,按左端点排 否则按右端点排
{
if (pos[x.l]==pos[y.l]) return x.r<y.r;
else return x.l<y.l;
}
int cmp_id(node x,node y)
{
return x.id<y.id;
}
long long gcd(long long x,long long y)
{
if (y==0) return x;
else return gcd(y,x%y);
}
void change(int i,int k)   //增加/减少k只第i个位置的袜子
{
ans-=sqr(s[c[i]]);
s[c[i]]+=k;
ans+=sqr(s[c[i]]);
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1; i<=n; i++) scanf("%d",&c[i]);
//分块
int N=sqrt(n);
for (int i=1;i<=n;i++)
{
pos[i]=(i-1)/N+1;
}
for (int i=1; i<=m; i++)
{
scanf("%lld%lld",&tr[i].l,&tr[i].r);
tr[i].id=i;
}
sort(tr+1,tr+1+m,cmp);
int l=1,r=0;
for (int i=1; i<=m; i++)
{
while (l<tr[i].l){change(l,-1);l++;}
while (l>tr[i].l){l--;change(l,1);}
while (r<tr[i].r){r++;change(r,1);}
while (r>tr[i].r){change(r,-1);r--;}
/*
先推一波公式[手动滑稽]
某种颜色选两只/所有方案
∑(s[i]*(s[i]-1))/ n*(n-1)
=∑(s[i]^2 -s[i]) /n*(n-1)
=∑s[i]^2 - n / n*(n-1)
*/
if (l==r)
{
tr[i].n=0;
tr[i].m=0;
continue;
}
tr[i].n=ans-(tr[i].r-tr[i].l+1);
tr[i].m=(tr[i].r-tr[i].l+1)*(tr[i].r-tr[i].l+1-1);
int x=gcd(tr[i].n,tr[i].m);
tr[i].n/=x;
tr[i].m/=x;
}
sort(t
4000
r+1,tr+1+m,cmp_id);
for (int i=1;i<=m;i++) printf("%lld/%lld\n",tr[i].n,tr[i].m);
return 0;
}


一道water 题:

bzoj1878 HH的项链

http://www.lydsy.com/JudgeOnline/problem.php?id=1878

求某一段区间中的种数

那么第一反应就是 : 大型数据结构走起

恭喜你 和我一样呢 [握个爪爪]

于是某谷上的题解出现了一堆

诸如 离线+树状数组 主席树 %%%%%%

看数据 修改很少 范围也很小

那150行的大型数据结构我还是选择50行的暴力把[捂脸]

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
struct node
{
int l,r,id,ans;
}tr[210000];
int s[1100000],pos[51000],col[51000],ans;
int cmp(node x,node y)
{
if (pos[x.l]==pos[y.l]) return x.r<y.r;
else return x.l<y.l;
}
int cmp_id(node x,node y)
{
return x.id<y.id;
}
void change(int i,int k)
{
s[col[i]]+=k;
if (s[col[i]]==0&&k==-1) ans--;
if (s[col[i]]==1&&k==1) ans++;
}
int main()
{
int n,m;
scanf("%d",&n);
for (int i=1;i<=n;i++){scanf("%d",&col[i]);}
int N=sqrt(n);
for (int i=1;i<=n;i++) pos[i]=(i-1)/N+1;
scanf("%d",&m);
for (int i=1;i<=m;i++)
{
scanf("%d%d",&tr[i].l,&tr[i].r);
tr[i].id=i;
}
sort(tr+1,tr+1+m,cmp);
int l=1,r=0;
for (int i=1;i<=m;i++)
{
while (l<tr[i].l) {change(l,-1);l++;}
while (l>tr[i].l) {l--;change(l,1);}
while (r<tr[i].r) {r++;change(r,1);}
while (r>tr[i].r) {change(r,-1);r--;}
tr[i].ans=ans;
}
sort(tr+1,tr+1+m,cmp_id);
for (int i=1;i<=m;i++) printf("%d\n",tr[i].ans);
return 0;
}


带修莫队:

bzoj 2120 数颜色

emm……

假想你有一台时光机 可以跳到不同的时间~

加多一个时间time 即是第几次修改之后的询问

当左右区间都一样的时候 看tim

然后接着一通乱跳就好

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
struct node1
{
int l,r,id,tim,ans;
}tr[210000];
struct node2
{
int x,y,ip;
//把原来是颜色为x的改为颜色为y  位置为ip
}c[1100];
int s[1100000],pos[11000],col[11000],now[11000],ans;
bool cmp(node1 x,node1 y)//左右区间同块看修改
{
if (pos[x.l]!=pos[y.l]) return x.l<y.l;
else if (pos[x.r]!=pos[y.r]) return x.r<y.r;
else return x.tim<y.tim;
}
bool cmp_id(node1 x,node1 y)
{
return x.id<y.id;
}
void change(int i,int k)
{
s[col[i]]+=k;
if (s[col[i]]==0&&k==-1) ans--;
if (s[col[i]]==1&&k==1) ans++;
}
int l=1,r=0,tim=0;
void tchange(int x,int y)//时间修改 把位置为x 的颜色改为y
{
if(l<=x&&x<=r) //当这个修改在区间中 即会影响ans 就要change一波
{
change(x,-1);
col[x]=y;
change(x,1);
}
else col[x]=y;
}
int main()
{
int n,m,na=0,nc=0;
//na:询问次数(ask) nc:改变次数(change)
char st[5];
scanf("%d",&n);
scanf("%d",&m);
for (int i=1;i<=n;i++){scanf("%d",&col[i]);now[i]=col[i];}
//一如既往的分块
int N=sqrt(n);
for (int i=1;i<=n;i++) pos[i]=(i-1)/N+1;
for (int i=1;i<=m;i++)
{
int x,y;
scanf("%s",st+1);
scanf("%d%d",&x,&y);
if (st[1]=='Q') {tr[++na].l=x;tr[na].r=y;tr[na].tim=nc;tr[na].id=na;}//记得记录当前时间
else {c[++nc].x=now[x];c[nc].y=y;c[nc].ip=x;now[x]=y;}
}//now的作用就体现出来了:我当前修改的时候队列是什么样的(有可能同一位置修改多次)
sort(tr+1,tr+1+na,cmp);
for (int i=1;i<=na;i++)
{
while (tim<tr[i].tim) {tim++;tchange(c[tim].ip,c[tim].y);}//坐时光机到未来 修改
while (tim>tr[i].tim) {tchange(c[tim].ip,c[tim].x);tim--;}//坐时光机到过去 改回来
//调区间
while (l<tr[i].l) {change(l,-1);l++;}
while (l>tr[i].l) {l--;change(l,1);}
while (r<tr[i].r) {r++;change(r,1);}
while (r>tr[i].r) {change(r,-1);r--;}
tr[i].ans=ans;
}
sort(tr+1,tr+1+na,cmp_id);
for (int i=1;i<=na;i++) printf("%d\n",tr[i].ans);
return 0;
}


缺一个树上莫队……有空再来写把T T
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: