bzoj3529: [Sdoi2014]数表

题目:http://www.lydsy.com/JudgeOnline/problem.php?id=3529
思路:这题的思路还是很好的。。。实际上是披上了数论外衣的数据结构狼。。。
首先我们化简这个式子,得到:

i=1nj=1mf(gcd(i,j))[f(gcd(i,j))<=a]

=i=1min(n,m)nimid|if(d)μ(id)[f(d)<=a]

按照一般的思路,因为 a 的限制,我们离线按a排序,然后每次将修改的值插入到树状数组中,然后单次计算 O(nlogn)
根据调和级数我们总的复杂度是: O(nlog2n+Qnlogn) ,当然,如果强制在线我们将树状数组换成主席树就可以了,复杂度不变。
作为一个正常人我们是不会满足于这么不优美的复杂度的。。。下面我来口胡一番离线复杂度更科学的做法:
通过前面我们可以体会的这根本就是一道数据结构题嘛。。。我们要完成的操作无非是区间和查询,单点修改
考虑分块,我们维护后面那一个东西,设两个参数 A,B 我们还是 a 从小到大枚举,当d<=A时我们暴力每次修改然后将数组扫一遍,这一步复杂度: O(An)
d>A 时,我们定期重构,暴力修改,维护一个计数器 cnt 表示当前修改了的不同位置有 cnt 个,将这些位置及其贡献存下来,当 cnt>B 时将贡献暴力添加入数组扫描一遍,查询的时候分为两个部分:一个是原数组,另一个是你存下来位置的贡献,设 f[x]=ni=xni
复杂度是: O(An+Q(B+n)+f[nA]nB)
首先我们注意到 f[n]<=nlogn ,根据均值不等式(或者打表)我们能取到一个最优值,很明显这个复杂度是优于上面那个做法的。。。但常数不算太小不知道写起来能不能跑过。。。

代码:

#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<algorithm>
#define lowbit(x)(x&(-x))
#define N 100002
#define M 20002
using namespace std;
struct node { int n,m,a,ID;};
node q[M];
struct data { int val,ID; };
data f[N];
int cnt,mu[N],Rou[N],ans[M],prime[N],c[N],Q;
bool not_prime[N];
bool cmp(node x,node y){ return x.a < y.a;};
bool cmp1(data x,data y){ return x.val < y.val;};
void in(int &x){
    char c;
    while (!isdigit(c = getchar()));
    x = (c ^ 48);
    while (isdigit(c = getchar())) x = 10 * x + (c ^ 48);
}

void S_(){
    memset(not_prime,0,sizeof(not_prime));
    cnt = 0; mu[1] = 1; 
    for (int i = 2;i < N - 1; ++i){
        if (!not_prime[i]) prime[++cnt] = i,mu[i] = -1;
        for (int j = 1;j <= cnt; ++j)
          if (prime[j] * i >= N - 1) break;
          else {
            not_prime[prime[j] * i] = 1;
            if (i % prime[j]) mu[i * prime[j]] = -mu[i];
            else { mu[i * prime[j]] = 0; break; }
          }
    }
    memset(Rou,0,sizeof(Rou));
    for (int i = 1;i < N - 1; ++i)
      for (int j = i;j < N - 1; j += i)
        Rou[j] += i;
    for (int i = 1;i < N - 1; ++i)
       f[i].val = Rou[i],f[i].ID = i;
    sort(f + 1,f + N - 2 + 1,cmp1);
}

void add(int pos,int v){
    for (int x = pos;x < N - 1; x += lowbit(x)) c[x] += v;
}

int query(int pos){
    int sum = 0;
    for (int x = pos;x;x -= lowbit(x)) sum += c[x];
    return sum; 
}

void add_it(int x){
    for (int j = x,t = 1;j < N - 1; j += x,++t)
      add(j,Rou[x] * mu[t]);
}

int main(){
    S_();
    in(Q);
    //scanf("%d",&Q);
    for (int i = 1;i <= Q; ++i){
      //scanf("%d%d%d",&q[i].n,&q[i].m,&q[i].a);
      in(q[i].n); in(q[i].m); in(q[i].a);
      q[i].ID = i;
      if (q[i].n > q[i].m) swap(q[i].n,q[i].m); }
    sort(q + 1,q + Q + 1,cmp);
    int j = 1;
    memset(c,0,sizeof(c));
    for (int i = 1;i <= Q; ++i){
        while (j < N - 1&&f[j].val <= q[i].a) add_it(f[j++].ID);
        int ans_ = 0,n = q[i].n,m = q[i].m;
        for (int o1 = 1,o2;o1 <= n; o1 = o2 + 1){
            o2 = min(n / (n / o1),m / (m / o1));
            ans_ += (n / o1) * (m / o1) * (query(o2) - query(o1 - 1));
        }
        ans[q[i].ID] = ans_;
    }
    for (int i = 1;i <= Q; ++i) printf("%d\n",ans[i]&((1 << 31) - 1));
    return 0;
}

总结:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值