洛谷 P4108 / loj 2119 [HEOI2015] 公约数数列 题解【分块】

作者: wjyyy 分类: 分块,解题报告 发布时间: 2019-03-04 21:05

点击量:44

看样子分块题应该做的还不够。

题目描述

设计一个数据结构. 给定一个正整数数列 \(a_0, a_1, \ldots , a_{n-1}\),你需要支持以下两种操作:

  1. MODIFY id x: 将 \(a_{\mathrm{id}}\) 修改为 $x$.
  2. QUERY x: 求最小的整数 \(p(0 \leq p < n)\),使得 \(\gcd(a_0, a_1, …, a_p) \cdot \operatorname{XOR}(a_0, a_1, …, a_p) = x\). 其中 \(\operatorname{XOR}(a_0, a_1, …, a_p)\) 代表 \(a_0, a_1, \ldots , a_p\) 的异或和,\(\gcd\) 表示最大公约数。

输入输出格式

输入格式:

输入数据的第一行包含一个正整数 \(n\)。

接下来一行包含 \(n\) 个正整数 \(a_0, a_1, …, a_{n-1}\)。

之后一行包含一个正整数 \(q\),表示询问的个数。

之后 \(q\) 行,每行包含一个询问。格式如题目中所述。

输出格式:

对于每个 QUERY 询问,在单独的一行中输出结果。如果不存在这样的 \(p\),输出 no.

输入输出样例

样例输入:

10
1353600 5821200 10752000 1670400 3729600 6844320 12544000 117600 59400 640
10
MODIFY 7 20321280
QUERY 162343680
QUERY 1832232960000
MODIFY 0 92160
QUERY 1234567
QUERY 3989856000
QUERY 833018560
MODIFY 3 8600
MODIFY 5 5306112
QUERY 148900352

输出样例:

6
0
no
2
8
8

数据范围与约定

对于 \(100\%\) 的数据,\(n \leq100000, \ q \leq 10000, \ a_i \leq 10^9 (0 \leq i < n)\),

QUERY x 中的 \(x \leq 10^{18}\),

MODIFY id x 中的 \(0 \leq \mathrm{id} < n, \ 1 \leq x \leq 10^9\)。

题解:

感觉需要推导的东西还是比较多的。但是以后要记着看到奇怪的(?)数据范围时要想到分块。

这个题有一定的特点,就是两个表达式都是前缀形式。对于 \(\gcd\) 而言,它一定是递减的,而且不同的数值最多只有 \(O(\log a_i)\) 个,因为每次至少会变为原来的 \(\frac 12\)。因此产生变化的位置是可以考虑枚举的。

注意到询问是 \(10000\) 的,不是很大。我们对原区间进行 \(O(\sqrt n)\) 分块。

因为要找的是最小的,所以应该从前往后枚举。对于 \(\gcd\) 整块不变的那些块,我们只需要找到其中是否存在异或前缀和恰好为 \(x/\gcd\) 的。这一询问可以用 std::map 来解决。

当涉及单点修改时,我们只需要对当前块中的 map 进行修改就可以了。

实际上 map 中存的是当前块中的异或前缀和,由于我们每次查询都是从前往后查,可以积累一定的信息。因此不必也不能维护全局前缀和,不然修改时影响的范围太大。

不过分块的细节挺多的,包括边界一类的。还要注意复杂度,不能把 gcd() 函数当 \(O(1)\) 的操作用啊啊啊啊啊啊…= =

时间复杂度 \(O(n\log a_i+q\sqrt n\log a_i)\)。

Code:

#include<cstdio>
#include<cstring>
#include<map>
#include<cmath>
#define inf 2147438647
using std::map;
int gcd(int x,int y)
{
    while(y)
    {
        int t=y;
        y=x%y;
        x=t;
    }
    return x;
}
map<int,int> f[400];
int a[400][400],g[400][400];
int G[400],sum[400];
int main()
{
    int n,m,u;
    scanf("%d",&n);
    int b=sqrt(n),tmp=0;
    for(int i=1;i<=n;++i)
    {
        int x=(i-1)/b,y=(i-1)%b+1;
        scanf("%d",&a[x][y]);
        tmp^=a[x][y];
        g[x][y]=gcd(a[x][y],g[x][y-1]);
        G[x]=g[x][y];
        if(f[x].find(tmp)==f[x].end())
            f[x][tmp]=y;
        if(x!=i/b)
        {
            sum[x]=tmp;
            g[x+1][0]=G[x];
            tmp=0;
        }
    }
    scanf("%d",&m);
    char op[100];
    int tot=ceil((double)n/b)-1;
    while(m--)
    {
        scanf("%s",op);
        if(op[0]=='M')
        {
            scanf("%d",&u);
            ++u;
            int x=(u-1)/b,y=(u-1)%b+1;
            scanf("%d",&a[x][y]);
            f[x].clear();
            tmp=0;
            g[x][0]=x?G[x-1]:0;
            for(int j=1;j<=b;++j)
            {
                tmp^=a[x][j];
                if(f[x].find(tmp)==f[x].end())
                    f[x][tmp]=j;
                g[x][j]=gcd(a[x][j],g[x][j-1]);
                G[x]=g[x][j];
            }
            sum[x]=tmp;
        }
        else
        {
            long long k;
            scanf("%lld",&k);
            int flag=0;
            tmp=0;
            int gtmp=a[0][1];
            for(int i=0;i<=tot;++i)
            {
                if(!i||G[i]!=G[i-1])//暴力
                {
                    for(int j=1;j<=b;++j)
                    {
                        tmp^=a[i][j];
                        gtmp=g[i][j];
                        if((long long)tmp*gtmp==k)
                        {
                            flag=1;
                            printf("%d\n",i*b+j-1);
                            break;
                        }
                    }
                    if(flag)
                        break;
                }
                else
                {
                    gtmp=G[i];
                    if(k%gtmp==0&&k/gtmp<inf&&f[i].find(k/gtmp^tmp)!=f[i].end())
                    {
                        printf("%d\n",i*b+f[i][k/gtmp^tmp]-1);
                        flag=1;
                        break;
                    }
                    tmp^=sum[i];
                }
            }
            if(flag)
                continue;
            puts("no");
        }
    }
    return 0;
}

说点什么

avatar
  Subscribe  
提醒
/* */