Bzoj3853 GCD Array

Time Limit: 6 Sec  Memory Limit: 64 MB
Submit: 328  Solved: 125

Description

Teacher Mai finds that many problems about arithmetic function can be reduced to the following problem:
Maintain an array a with index from 1 to l. There are two kinds of operations:
  1. Add v to ax for every x that gcd(x,n)=d.
  2. Query Sigma(Xi) (i<=1<=X)

Input

There are multiple test cases, terminated by a line "0 0".
For each test case, the first line contains two integers l,Q(1<=l,Q<=5*10^4), indicating the length of the array and the number of the operations.
In following Q lines, each line indicates an operation, and the format is "1 n d v" or "2 x" (1<=n,d,v<=2*10^5,1<=x<=l).

Output

For each case, output "Case #k:" first, where k is the case number counting from 1.
Then output the answer to each query.

 

Sample Input

6 4
1 4 1 2
2 5
1 3 3 3
2 3
0 0

Sample Output

Case #1:
6
7

HINT

Source

 

数学问题 莫比乌斯反演

 

我们注意到每次修改的位置是不连续的,很难用数据结构统一维护。但是这个数据范围看上去必须用数据结构维护,那么我们就得想办法让每次修改/查询的部分连续起来。

方法一:分块。

  将修改的数量分块,如果$ gcd(x,n)=d $的个数大于 $ sqrt(i) $个,暴力修改,如果小于$sqrt(i)$,分块修改,有一部分带修改点构成了有规律的区间。

  常数优秀的话应该可以过(口胡,没尝试过)

 

方法二:莫比乌斯反演

  我们把题目中各个关键条件都写在草稿纸上,这样有很大几率会看到类似$ v [gcd(x,n)=d]$ 的式子

  看到这个形式,我的内心充满波动,甚至想要反演!

  试着推一下式子:
$$ v[gcd(x,n)=d] $$
$$ =v[gcd(x/d,n/d)=1] $$
$$ =v * \sum_{k|\frac{x}{d} , k|\frac{n}{d}} \mu(k)$$

    $[k|\frac{x}{d}] \mu(k)$
变形成
    $[kd|x] \mu(k)$
那么我们就可以O($\sqrt n$)地枚举 $\frac{n}{d}$的因数k,再单点修改每一个 $kd|x$ 的位置。

(x是不确定的位置,修改了每一个kd,就等于修改了所有受影响的x)
嗯?我们好像并没有解决单点修改的问题?
但我们可以区间查询了!
设原来要维护的数组是a[],现在维护的数组是c[],
那么有
$$a[i]=\sum_{j|i}c[j]$$
于是
$$ans=\sum_{i=i}^{x} c[i] =\sum_{i=i}^{x} \sum_{j|i}c[j] $$
$$ans=\sum_{j=1}^{x}c[j]* \lfloor \frac{x}{j} \rfloor$$
可以应用分块加速的套路。
用数据结构维护c[j]的前缀和即可。

 

一个脑洞:

  如果每次给定k和b,修改满足"kx+b<=n"的"kx+b"位置,要怎么玩?(k,b,x都是整数)

  “有问必答不知道”

另一个脑洞:

  如果每次修改$ lcm(x,n)=d $ 的位置,要怎么玩?(x,n,d都是整数)

  我们试着推一下:

  $$ v[lcm(x,n)=d] $$
  $$ v[gcd(x,n)=\frac{x*n}{d} ]$$
  $$ v[gcd(d/n,d/x)=1]$$
  $$ =v * \sum_{k|\frac{d}{x} , k|\frac{d}{n}} \mu(k)$$

  我们发现这次x到了分母的位置,且d每次修改的时候都会变,维护起来就很麻烦……
  嗯……我编不下去了!(逃)

 

原题代码:

 1 #include<iostream>
 2 #include<algorithm>
 3 #include<cstring>
 4 #include<cstdio>
 5 #include<cmath>
 6 #include<vector>
 7 #define LL long long
 8 using namespace std;
 9 const int mxn=200010;
10 int read(){
11     int x=0,f=1;char ch=getchar();
12     while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
13     while(ch>='0' && ch<='9'){x=x*10+ch-'0';ch=getchar();}
14     return x*f;
15 }
16 int n,Q;
17 LL t[mxn];
18 void add(int x,int v){
19     while(x<=n){t[x]+=v;x+=x&-x;}return;
20 }
21 LL smm(int x){
22     LL res=0;
23     while(x){res+=t[x];x-=x&-x;}
24     return res;
25 }
26 //
27 int pri[mxn],mu[mxn],cnt=0;
28 bool vis[mxn];
29 vector<int>ve[mxn];
30 void init(){
31     mu[1]=1;
32     for(int i=2;i<mxn;i++){
33         if(!vis[i]){
34             pri[++cnt]=i;
35             mu[i]=-1;
36         }
37         for(int j=1;j<=cnt && pri[j]*i<mxn;j++){
38             vis[pri[j]*i]=1;
39             if(i%pri[j]==0){mu[i*pri[j]]=0;break;}
40             mu[pri[j]*i]=-mu[i];
41         }
42     }
43     for(int i=1;i<mxn;i++)
44         for(int j=i;j<mxn;j+=i)
45             ve[j].push_back(i);
46     return;
47 }
48 LL calc(int x){
49     LL res=0;
50     for(int i=1,pos;i<=x;i=pos+1){
51         int y=x/i; pos=x/y;
52         res=res+(smm(pos)-smm(i-1))*(LL)y;
53     }
54     return res;
55 }
56 int main(){
57     int i,j;
58     init();
59     int cas=0;
60     while(scanf("%d%d",&n,&Q)!=EOF){
61         if(!n && !Q)break;
62         printf("Case #%d:\n",++cas);
63         memset(t,0,sizeof t);
64         int op,x,v,d;
65         while(Q--){
66             op=read();
67             if(op==1){
68                 x=read();d=read();v=read();
69                 if(x%d==0){
70                     int st=x/d;
71                     for(j=0;j<ve[st].size();j++){
72                         int dx=ve[st][j];
73                         add(dx*d,v*mu[dx]);
74                     }
75                 }
76             }
77             else{
78                 x=read();
79                 LL ans=calc(x);
80                 printf("%lld\n",ans);
81             }
82         }
83     }
84     return 0;
85 }

 

  

posted @ 2017-06-12 09:33  SilverNebula  阅读(361)  评论(0编辑  收藏  举报
AmazingCounters.com