学习莫队算法、、、

本以为莫队算法是很简单的东西、、、
但是学完了吓得我筷子都掉地上了、、、
先来看看我的提交记录、、、

再看BZOJ:

其实错了这么多次确实不应该交了、、、
但是、、、没想到的是、、、
我的模板都是错的、、、
先来讲讲莫队算法、、、
离线算法,不带修改,多次询问,[l,r]可以O(1)求出[l,r + 1],[l,r - 1],[l - 1,r],[l + 1,r]。
那么我们可以用莫队算法来做、、、
大概是这样的:
我们把每次询问:[l,r]看成是一个二维平面的点a[l,r],所以所有询问就能看成:
二维平面的一群散点、、、
现在我们从一个点出发,花费最小的代价使得每个散点都到达、、
而代价就是每个点之间的manhattan距离、、、
即:
求二维平面的点的manhattan距离最小生成树、、、
均摊复杂度是O(nsqrt(n)),但是编程复杂度过高,不推(hui)荐(xie)。


另一种想法是分块思想:
将原来的序列分成sqrt(n)块,那么我们只要对不在一块中的询问按照左端点排序,在同一块中的询问按照右侧端点排序即可、、、

均摊复杂度是O(nsqrt(n)),简单证明一下大概是这个意思:

假设我们这两个点在同一块,显然移动询问区间的代价<sqrt(n),
考虑这两个点相差仅仅一块,移动询问区间的代价大概是2 * sqrt(n),
如果相差很远很远……那中间一定还有点、、、
均摊来讲是O(nsqrt(n))、、、

现在来讲讲我写莫队挂掉两次的故事:
WA && TLE:
先来讲讲我是怎么TLE的……
请注意我之前分块思想的一句话:
将原来的序列分成sqrt(n)块。
将原来的序列分成sqrt(n)块。
将原来的序列分成sqrt(n)块。
是啊怎么了?
然后我傻乎乎地写了:
int BLOCK = sqrt(n);
Q[i].Block = i / BLOCK + 1;
实际上:
Q[i].Block = Q[i].l/ BLOCK + 1;
所以说不要写混……
区别大概就是:
一个TLE一个AC的区别、、、
然后继续讲一下迷之WA:

#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#define Rep(i,n) for(int i = 1; i <= n ; i ++)
#define RepG(i,x) for(int i = head[x] ;~ i ; i = edge[i].next)
#define Rep_d(i,n) for(int i = n ; i > 0 ; i --)
#define Rep_0(i,n) for(int i = 0 ; i < n ; i ++)
#define RD(i,x,n) for(int i = x; i <= n ; i ++)
#define CLR(a,b) memset(a,b,sizeof(a))
#define fl edge[i].f
#define vfl edge[i^1].f
#define v edge[i].to
#define N 50005
using namespace std;
const int inf = 1 << 30;
typedef long long ll;
int n,m,seq[N],BLK[N];
int read(){
    char ch = getchar();
    while(ch < '0' || ch > '9')ch = getchar ();
    int x = 0;
    while(ch >= '0' && ch <= '9')x = 10 * x + ch - '0',ch = getchar ();
    return x;
}
ll num[N];
ll gcd(int a,int b){return b ? gcd(b,a % b) : a;}
struct QRY{
    int l,r,idx;
}Q[50005];
ll ans = 0;
ll Res[N][2];
void Upd(int x,int d){
    ans -= num[seq[x]] * num[seq[x]];
    num[seq[x]] += d;
    ans += num[seq[x]] * num[seq[x]];
}
bool cmp(QRY A,QRY B){
    if(BLK[A.l] ==  BLK[B.l])
        return A.r < B.r;
    return A.l < B.l;
}
int main()
{
    while(~scanf("%d%d",&n,&m)){
        CLR(num,0);
        int BLOCK = sqrt(n);
        Rep(i,n){
            seq[i] = read();
            BLK[i] = (i - 1) / BLOCK + 1;
        }
        Rep(i,m) 
            Q[i].l = read(),Q[i].r = read(),Q[i].idx = i;
        sort(Q + 1,Q + 1 + m,cmp);
        int l = 1,r = 0;
        Rep(i,m)
        {
            if(Q[i].l == Q[i].r){
                Res[Q[i].idx][0] = 0;
                Res[Q[i].idx][1] = 1;
                continue;
            }

            /*if(qu[i].l==qu[i].r)
            {
                up[id]=0,dw[id]=1;
                continue;
            }
            if(r < Q[i].r)
            {
                for(int j = r + 1;j <= Q[i].r;j ++) //R + 1 - > QR ++
                    Upd(j,1);
            }
            else
            {
                for(int j = r;j > Q[i].r;j --)
                    Upd(j,-1);
            }
            r = Q[i].r;
            if(l < Q[i].l)
            {
                for(int j = l;j < Q[i].l;j ++)
                    Upd(j,-1);
            }
            else
            {
                for(int j = l - 1;j >= Q[i].l;j --) 
                    Upd(j,1);
            }
            l = Q[i].l;
            */
            while(l > Q[i].l)Upd(-- l,1);//printf("EXTEND L:%d\n",l);
            while(r < Q[i].r)Upd(++ r,1);//,printf("EXTEND R:%d\n",r);
            while(l < Q[i].l)Upd(l ++,-1);//,printf("DISEXTEND L:%d\n",l - 1);
            while(r > Q[i].r)Upd(r --,-1);//,printf("DISEXTEND R:%d\n",r + 1);
            //printf("R:%d QR:%d\n",r,Q[i].r);
            //printf("L : %d QL : %d\n",l,Q[i].l);
            ll UP = ans - Q[i].r + Q[i].l - 1;
            ll DOWN = (ll)(Q[i].r - Q[i].l + 1) * (Q[i].r - Q[i].l);
            ll dd = gcd(UP,DOWN);
            Res[Q[i].idx][0] = UP/dd;
            Res[Q[i].idx][1] = DOWN/dd;
            //puts("THIS QUERY IS END.");
        }
        Rep(i,m)
            printf("%lld/%lld\n",Res[i][0],Res[i][1]);  
    }
    return 0;
}

这个无论是I64d还是lld,无论是BZOJ还是清橙、、、都是WA,现在还不知道错误到底在哪、、、按说上面和下面的语句都是等效的、、、
等一会再调一下、、、

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值