• 专题训练之莫队算法


    推荐博客/专栏:https://blog.csdn.net/xianhaoming/article/details/52201761莫队算法讲解(含树上莫队)

    https://blog.csdn.net/hzj1054689699/article/details/51866615莫队算法

    https://zhuanlan.zhihu.com/p/25017840莫队算法

    例题及讲解:(BZOJ2038)https://www.luogu.org/problemnew/show/P1494

     讲解:https://www.cnblogs.com/MashiroSky/p/5914637.html

    https://blog.csdn.net/xym_csdn/article/details/50889293

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 #include<cmath>
     5 using namespace std;
     6 typedef long long ll;
     7 const ll maxn=5e4+10;
     8 struct node{
     9     ll l,r,id,belong;
    10     ll a,b;
    11 }arr[maxn];
    12 ll num[maxn],a[maxn],ans;
    13 
    14 bool cmp1(node a,node b)
    15 {
    16     if ( a.belong==b.belong ) return a.r<b.r;
    17     return a.belong<b.belong;
    18 }
    19 
    20 bool cmp2(node a,node b)
    21 {
    22     return a.id<b.id;
    23 }
    24 
    25 ll gcd(ll x,ll y)
    26 {
    27     if ( y==0 ) return x;
    28     return gcd(y,x%y);
    29 }
    30 
    31 void update(ll p,ll val)
    32 {
    33     ans-=num[a[p]]*num[a[p]];
    34     num[a[p]]+=val;
    35     ans+=num[a[p]]*num[a[p]];
    36 }
    37 
    38 int main()
    39 {
    40     ll n,m,i,j,k,x,y,z,l,r,sz;
    41     while ( scanf("%lld%lld",&n,&m)!=EOF ) {
    42         for ( i=1;i<=n;i++ ) scanf("%lld",&a[i]);
    43         sz=sqrt(n);
    44         for ( i=1;i<=m;i++ ) {
    45             scanf("%lld%lld",&arr[i].l,&arr[i].r);
    46             arr[i].id=i;
    47             arr[i].belong=(arr[i].l-1)/sz+1;
    48         }
    49         sort(arr+1,arr+1+m,cmp1);
    50         l=1;
    51         r=0;
    52         ans=0;
    53         memset(num,0,sizeof(num));
    54         for ( i=1;i<=m;i++ ) {
    55             for ( ;r<arr[i].r;r++ ) update(r+1,1);
    56             for ( ;r>arr[i].r;r-- ) update(r,-1);
    57             for ( ;l>arr[i].l;l-- ) update(l-1,1);
    58             for ( ;l<arr[i].l;l++ ) update(l,-1);
    59             if ( arr[i].l==arr[i].r ) {
    60                 arr[i].a=0;
    61                 arr[i].b=1;
    62                 continue;
    63             }
    64             arr[i].a=ans-(arr[i].r-arr[i].l+1);
    65             arr[i].b=(ll)(arr[i].r-arr[i].l+1)*(arr[i].r-arr[i].l);
    66             k=gcd(arr[i].a,arr[i].b);
    67             arr[i].a/=k;
    68             arr[i].b/=k;
    69         }
    70         sort(arr+1,arr+1+m,cmp2);
    71         for ( i=1;i<=m;i++ ) printf("%lld/%lld
    ",arr[i].a,arr[i].b);
    72         
    73     } 
    74     return 0;
    75 }
    BZOJ2038

    练习题:

    1.(HDOJ4858)http://acm.hdu.edu.cn/showproblem.php?pid=4858

    分析:图的分块。设点i的点权为val[i],与点i相邻的项目的能量值之和为sum[i]。将图中的点分为重点和轻点,重点是那些边的度数超过sqrt(m)(该值可以自己规定)的点,除了重点剩下的点都是轻点。对于构图,重点只和重点建边,轻点可以和所有点建边。每次更新,对于重点i和轻点来说来说都是更新自己的val[i]和相邻点的sum[i]。而对于查询操作来说,重点直接输出sum[i],而轻点则采用暴力做法:遍历其每一个相邻点,答案累加上相邻的val[i]。采用的思想是分摊复杂度的思想

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 #include<vector>
     5 #include<cmath>
     6 using namespace std;
     7 typedef long long ll;
     8 const ll maxn=1e5+100;
     9 struct edge{
    10     ll u,v;
    11 }arr[maxn];
    12 ll val[maxn],du[maxn],sum[maxn];
    13 bool ok[maxn];
    14 vector<ll>G[maxn];
    15 
    16 int main()
    17 {
    18     ll T,i,j,k,x,y,z,ans,cnt,n,m,sz,u,v,op,q;
    19     scanf("%lld",&T);
    20     while ( T-- ) {
    21         scanf("%lld%lld",&n,&m);
    22         for ( i=1;i<=n;i++ ) {
    23             G[i].clear(); 
    24             ok[i]=false;
    25             du[i]=val[i]=sum[i]=0;
    26         }
    27         for ( i=1;i<=m;i++ ) {
    28             scanf("%lld%lld",&arr[i].u,&arr[i].v);
    29             du[arr[i].u]++;
    30             du[arr[i].v]++;
    31         }
    32         sz=sqrt(m);
    33         for ( i=1;i<=n;i++ ) {
    34             if ( du[i]>sz ) ok[i]=true;
    35         }
    36         for ( i=1;i<=m;i++ ) {
    37             x=arr[i].u;
    38             y=arr[i].v;
    39             if ( ok[x] ) {
    40                 if ( ok[y] ) {
    41                     G[x].push_back(y);
    42                     G[y].push_back(x);
    43                 }
    44                 else {
    45                     G[y].push_back(x);
    46                 }
    47             }
    48             else {
    49                 if ( ok[y] ) {
    50                     G[x].push_back(y);
    51                 }
    52                 else {
    53                     G[x].push_back(y);
    54                     G[y].push_back(x);
    55                 }
    56             }
    57         }
    58         scanf("%lld",&q);
    59         while ( q-- ) {
    60             scanf("%lld",&op);
    61             if ( op==0 ) {
    62                 scanf("%lld%lld",&u,&x);
    63                 val[u]+=x;
    64                 for ( i=0;i<G[u].size();i++ ) {
    65                     v=G[u][i];
    66                     sum[v]+=x;
    67                 }
    68             }
    69             else {
    70                 scanf("%lld",&u);
    71                 if ( ok[u] ) printf("%lld
    ",sum[u]);
    72                 else {
    73                     ans=0;
    74                     for ( i=0;i<G[u].size();i++ ) {
    75                         v=G[u][i];
    76                         ans+=val[v];
    77                     }
    78                     printf("%lld
    ",ans);
    79                 }
    80             }
    81         }
    82     }
    83     return 0;
    84 }
    HDOJ4858

    2.(HDOJ4467)http://acm.hdu.edu.cn/showproblem.php?pid=4467

    题意:给你n个点(每个点都有一个颜色,0代表黑色,1代表白色),m条边,每条边有一个权值.现在有有两个操作,一个是修改某个点的颜色(白变成黑/黑变成白),另外一个是询问那些边的两个端点都为指定颜色的权值总和

    分析:采用上题相同的思想。将所有点分为重点和轻点,但是这次重点和重点之前的边要建在一个图中,剩余的边要建在另一个图中。对于最后访问的颜色,只有三种情况黑+黑(求和为0),黑+白(求和为1),白+白(求和为2),所以用a[0],a[1],a[2]分别对应的答案。对于重点i设置一个sum[i][2],sum[i][0]表示所有与他相邻且颜色为0(黑)的点的边权之和,sum[i][1]同理。更新时,对于重点i来说拿sum[i][0]和sum[i][1]去直接更新a数组,同时将其相邻的重点的sum值进行修改。而对于轻点i来说,遍历所有与i相连的边,暴力更新a数组,而当其相邻点为重点时则需要更新一下重点的sum数组。对于查询操作,直接输出a数组中的值即可

      1 #include<cstdio>
      2 #include<cstring>
      3 #include<algorithm>
      4 #include<vector>
      5 #include<cmath>
      6 using namespace std;
      7 typedef long long ll;
      8 const ll maxn=1e5+10;
      9 ll sum[maxn][2],a[5],color[maxn],du[maxn];
     10 bool ok[maxn];
     11 struct Edge{
     12     ll x,y,val;
     13 }arr[maxn],arr_[maxn];
     14 struct edge{
     15     ll v,val;
     16     edge(ll _v=0,ll _val=0):v(_v),val(_val) {}
     17 };
     18 vector<edge>G[maxn],G_[maxn];
     19 
     20 bool cmp(Edge x,Edge y)
     21 {
     22     if ( x.x==y.x ) return x.y<y.y;
     23     return x.x<y.x;
     24 }
     25 
     26 int main()
     27 {
     28     ll n,m,i,j,k,x,y,z,sz,cnt,q,ans,h=0;
     29     char op[10];
     30     while ( scanf("%lld%lld",&n,&m)!=EOF ) {
     31         for ( i=1;i<=n;i++ ) {
     32             sum[i][0]=sum[i][1]=0;
     33             ok[i]=false;
     34             du[i]=0;
     35             G[i].clear();
     36             G_[i].clear();
     37         }
     38         memset(a,0,sizeof(a));
     39         for ( i=1;i<=n;i++ ) scanf("%lld",&color[i]);
     40         for ( i=1;i<=m;i++ ) {
     41             scanf("%lld%lld%lld",&x,&y,&arr[i].val);
     42             if ( x>y ) swap(x,y);
     43             arr[i].x=x;
     44             arr[i].y=y;
     45             a[color[x]+color[y]]+=arr[i].val;
     46         }
     47         sort(arr+1,arr+1+m,cmp);
     48         cnt=0;
     49         for ( i=1;i<=m;i=j ) {
     50             for ( j=i+1;j<=m;j++ ) {
     51                 if ( arr[i].x==arr[j].x && arr[i].y==arr[j].y ) {
     52                     arr[i].val+=arr[j].val;
     53                 }
     54                 else break;
     55             }
     56             arr_[++cnt]=arr[i];
     57         }
     58         sz=sqrt(cnt);
     59         for ( i=1;i<=cnt;i++ ) {
     60             du[arr_[i].x]++;
     61             du[arr_[i].y]++;
     62         }
     63         for ( i=1;i<=n;i++ ) {
     64             if ( du[i]>sz ) ok[i]=true;
     65         }
     66         for ( i=1;i<=cnt;i++ ) {
     67             x=arr_[i].x;
     68             y=arr_[i].y;
     69             if ( ok[x] ) {
     70                 if ( ok[y] ) {
     71                     G_[x].push_back(edge(y,arr_[i].val));
     72                     G_[y].push_back(edge(x,arr_[i].val));
     73                     sum[x][color[y]]+=arr_[i].val;
     74                     sum[y][color[x]]+=arr_[i].val;
     75                 }
     76                 else {
     77                     G[y].push_back(edge(x,arr_[i].val));
     78                     sum[x][color[y]]+=arr_[i].val;
     79                 }
     80             }
     81             else {
     82                 if ( ok[y] ) {
     83                     G[x].push_back(edge(y,arr_[i].val));
     84                     sum[y][color[x]]+=arr_[i].val;
     85                 }
     86                 else {
     87                     G[x].push_back(edge(y,arr_[i].val));
     88                     G[y].push_back(edge(x,arr_[i].val));
     89                 }
     90             }
     91         }
     92         printf("Case %lld:
    ",++h);
     93         scanf("%lld",&q);
     94         while ( q-- ) {
     95             scanf("%s",op);
     96             if ( op[0]=='A' ) {
     97                 scanf("%lld%lld",&x,&y);
     98                 printf("%lld
    ",a[x+y]);
     99             }
    100             else {
    101                 scanf("%lld",&x);
    102                 if ( ok[x] ) {
    103                     a[color[x]+0]-=sum[x][0];
    104                     a[color[x]+1]-=sum[x][1];
    105                     a[1-color[x]+0]+=sum[x][0];
    106                     a[1-color[x]+1]+=sum[x][1];
    107                     for ( i=0;i<G_[x].size();i++ ) {
    108                         y=G_[x][i].v;
    109                         z=G_[x][i].val;
    110                         sum[y][color[x]]-=z;
    111                         sum[y][1-color[x]]+=z;
    112                     }
    113                 }
    114                 else {
    115                     for ( i=0;i<G[x].size();i++ ) {
    116                         y=G[x][i].v;
    117                         z=G[x][i].val;
    118                         a[color[x]+color[y]]-=z;
    119                         a[1-color[x]+color[y]]+=z;
    120                         if ( ok[y] ) {
    121                             sum[y][color[x]]-=z;
    122                             sum[y][1-color[x]]+=z;
    123                         }
    124                     }
    125                 }
    126                 color[x]=1-color[x];
    127             }
    128         }
    129     }
    130     return 0;
    131 }
    HDOJ4467

    3.(HDOJ5213)http://acm.hdu.edu.cn/showproblem.php?pid=5213

    题意:给我们n个数,然后给我们一个数字K,再给我们两个区间[L,R],[U,V],问我们从这两个区间里分别去两个数,有多少对取法,可以使得所取的两个数和为K。

    分析:莫队算法+容斥定理。因为莫队算法是知道[l,r]的值后可以求[l',r']的值,因为每次都会给两个区间,所以我们需要将两个不相关的区间想办法让他们产生连续。对于[x1,y1][x2,y2]我们这时候可以通过容斥定理将其变为F[x1,y1,x2,y2]=F[x1,y2]+F[y1+1,x2-1]-F[y1+1,y2]-F[x1,x2-1]将一次求解变成4个区间分别求解,每次在一个区间中求解有多少种取法可以使得两个数和为k

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 #include<cmath>
     5 using namespace std;
     6 typedef long long ll;
     7 const ll maxn=2e5+10;
     8 struct edge{
     9     ll x1,x2,y1,y2;
    10     ll ans;
    11 }arr_[maxn];
    12 struct node{
    13     ll l,r,fa;
    14     ll ans;
    15     bool ok;
    16 }arr[maxn];
    17 ll num[maxn],a[maxn],ans,belong[maxn],K;
    18 
    19 bool cmp1(node a,node b)
    20 {
    21     if ( belong[a.l]==belong[b.l] ) return a.r<b.r;
    22     return belong[a.l]<belong[b.l];
    23 }
    24 
    25 void update(ll p,ll val)
    26 {
    27     num[a[p]]+=val;
    28     if ( K>a[p] ) ans+=num[K-a[p]]*val;
    29 }
    30 
    31 int main()
    32 {
    33     ll n,m,i,j,k,x,y,z,l,r,sz,x1,x2,y1,y2;
    34     bool flag;
    35     while ( scanf("%lld",&n)!=EOF ) {
    36         scanf("%lld",&K);
    37         for ( i=1;i<=n;i++ ) scanf("%lld",&a[i]);
    38         sz=sqrt(n);
    39         for ( i=1;i<=n;i++ ) belong[i]=(i-1)/sz+1;
    40         scanf("%lld",&m);
    41         for ( i=1;i<=m;i++ ) {
    42             scanf("%lld%lld%lld%lld",&arr_[i].x1,&arr_[i].y1,&arr_[i].x2,&arr_[i].y2);
    43             arr_[i].ans=0;
    44             for ( j=0;j<4;j++ ) {
    45                 arr[i+j*m].fa=i;
    46                 arr[i+j*m].ans=0;
    47             }
    48             arr[i].ok=true;arr[i].l=arr_[i].x1;arr[i].r=arr_[i].y2;
    49             arr[i+m].ok=true;arr[i+m].l=arr_[i].y1+1;arr[i+m].r=arr_[i].x2-1;
    50             arr[i+m*2].ok=false;arr[i+m*2].l=arr_[i].x1;arr[i+m*2].r=arr_[i].x2-1;
    51             arr[i+m*3].ok=false;arr[i+m*3].l=arr_[i].y1+1;arr[i+m*3].r=arr_[i].y2;
    52         }
    53         sort(arr+1,arr+1+m*4,cmp1);
    54         l=1;
    55         r=0;
    56         ans=0;
    57         memset(num,0,sizeof(num));
    58         for ( i=1;i<=4*m;i++ ) {
    59             for ( ;r<arr[i].r;r++ ) update(r+1,1);
    60             for ( ;r>arr[i].r;r-- ) update(r,-1);
    61             for ( ;l>arr[i].l;l-- ) update(l-1,1);
    62             for ( ;l<arr[i].l;l++ ) update(l,-1);
    63             arr[i].ans=ans;
    64         }
    65         for ( i=1;i<=4*m;i++ ) {
    66             x=arr[i].fa;
    67             y=arr[i].ans;
    68             flag=arr[i].ok;
    69             if ( flag ) arr_[x].ans+=y;
    70             else arr_[x].ans-=y;
    71         }
    72         for ( i=1;i<=m;i++ ) printf("%lld
    ",arr_[i].ans);
    73         
    74     } 
    75     return 0;
    76 }
    HDOJ5213

    4.(HDOJ5145)http://acm.hdu.edu.cn/showproblem.php?pid=5145

    题意:一个人有n个女朋友,每个女朋友都有一个班级a[i],现有m个询问。每个询问有一个区间范围[L,R],表示这个人想要约[L,R]范围内的女生有几种约法。

    比如112 有112 121 211三种情况

    123 有123 132 213 231 312 321六种情况

    分析:给出公式,对于范围[L,R]来说可能的情况为(R-L+1)!/【(num[x1]!)*(num[x2]!)……*(num[xn]!)】  x1,x2,……,xn为[L,R]区间内出现过的所有不相同的数,num[]表示该数出现的次数

    即情况数=该区间范围内所有数的全排列/每个数各自的全排列的求和。

    因为出现取余操作,所以当出现除法操作时需要用到逆元(又因为mod=1e9+7为一个质数,所以考虑用费马小定理)

    因为有很多阶乘操作,所以通过预处理得到可能范围内所以数的阶乘(保存在d[i]中)和阶乘的逆元(保存在nd[i]中)

    因为答案的分子(即(R-L+1)!可在预处理后直接访问得到),所以在中间访问过程中只记录分母(记作ans)

    当num[a[p]]]++时,需要ans*=num[a[p]];当num[a[p]]--时,需要ans/=num[a[p]];

    可以将这个两个式子统一起来,当num[a[p]]发生改变时:1.ans*=nd[num[a[p]]] (本来是除,现在变成乘逆元)  2.num[a[p]]+=val (val可正可负)  3.ans*=d[num[a[p]]]

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 #include<cmath>
     5 using namespace std;
     6 typedef long long ll;
     7 const ll maxn=3e4+10;
     8 const ll N=3e4;
     9 const ll mod=1000000007;
    10 struct node{
    11     ll l,r,id,belong;
    12     ll sum;
    13 }arr[maxn];
    14 ll num[maxn],a[maxn],ans;
    15 ll d[maxn],nd[maxn];
    16 
    17 bool cmp1(node a,node b)
    18 {
    19     if ( a.belong==b.belong ) return a.r<b.r;
    20     return a.belong<b.belong;
    21 }
    22 
    23 bool cmp2(node a,node b)
    24 {
    25     return a.id<b.id;
    26 }
    27 
    28 ll quick(ll x,ll y)
    29 {
    30     ll sum=1;
    31     while ( y ) {
    32         if ( y&1 ) sum=(sum*x)%mod;
    33         x=(x*x)%mod;
    34         y/=2;
    35     }
    36     return sum%mod;
    37 }
    38 
    39 void init()
    40 {
    41     d[0]=nd[0]=1;
    42     for ( ll i=1;i<=N;i++ ) d[i]=(d[i-1]*i)%mod;
    43     for ( ll i=1;i<=N;i++ ) nd[i]=quick(d[i],mod-2);
    44 }
    45 
    46 void update(ll p,ll val)
    47 {
    48     ans=(ans*nd[num[a[p]]])%mod;
    49     num[a[p]]+=val;
    50     ans=(ans*d[num[a[p]]])%mod;
    51 }
    52 
    53 int main()
    54 {
    55     ll n,m,i,j,k,x,y,z,l,r,sz,T;
    56     scanf("%lld",&T);
    57     while ( T-- ) {
    58         scanf("%lld%lld",&n,&m);
    59         init();
    60         for ( i=1;i<=n;i++ ) scanf("%lld",&a[i]);
    61         sz=sqrt(n);
    62         for ( i=1;i<=m;i++ ) {
    63             scanf("%lld%lld",&arr[i].l,&arr[i].r);
    64             arr[i].id=i;
    65             arr[i].belong=(arr[i].l-1)/sz+1;
    66         }
    67         sort(arr+1,arr+1+m,cmp1);
    68         l=1;
    69         r=0;
    70         ans=1;
    71         memset(num,0,sizeof(num));
    72         for ( i=1;i<=m;i++ ) {
    73             for ( ;r<arr[i].r;r++ ) update(r+1,1);
    74             for ( ;r>arr[i].r;r-- ) update(r,-1);
    75             for ( ;l>arr[i].l;l-- ) update(l-1,1);
    76             for ( ;l<arr[i].l;l++ ) update(l,-1);
    77             arr[i].sum=d[arr[i].r-arr[i].l+1]*quick(ans,mod-2)%mod;
    78         }
    79         sort(arr+1,arr+1+m,cmp2);
    80         for ( i=1;i<=m;i++ ) printf("%lld
    ",arr[i].sum);
    81         
    82     } 
    83     return 0;
    84 }
    HDOJ5145

    小结:对于莫队算法的题目,首先需要确定出在一段区间内所求答案的公式是什么(/怎么计算),再去考虑当范围改变时答案应该如何改变。有时候给出的不是一段区间,需要学会将所有问题转化到一段区间范围内的求解。

  • 相关阅读:
    WCF进阶01 服务配置
    资源学习备忘 04
    .NET IoC主流框架 01:Castle Windsor Registering components onebyone
    IIS URL重写模块和ASP.NET路由组件比较
    资源学习备忘 03
    开源组件DockPanel Suite使用
    Dockable Window开源组件分享
    WCF进阶02 服务承载
    Prometheus 初探和配置(安装测试)
    TF31003错误的解决办法
  • 原文地址:https://www.cnblogs.com/HDUjackyan/p/8996172.html
Copyright © 2020-2023  润新知