A1306. apex(卓亮)

传送门:http://www.tsinsen.com/ViewGProblem.page?gpid=A1306
思路:来说一下我的傻逼做法。。。
考虑一个性质,设有区间 f[a,b] 如果 a,b 都是类似于 2i 的形式的话,那么
f[a,b]=2f[a+34,b4] 这样每次区间长度都会除以 4 ,我们递归的做下去直到底层区间长为1 2 我们暴力log(w)计算单点,那么我们怎么找到这些个分割点呢?一开始我从小到大枚举二进制位,发现貌似这样并不可以,那么我们还有更强大的工具,线段树。建立一颗虚拟的 2x 的线段树,那么我们相当于要查询一个区间,也就是将 [1,n] 裂成 logw 个区间,这样可以保证分界点可以递归的做下去,事实上,线段树的做法本质上是从大到小枚举二进制位,这样也解释了正确性。
但这样复杂度是 O(qlog2w) 的,这样还不够优秀
我在青橙上是过了的,但 bzoj 时限是青橙的一半,我无论怎么优化都无法过去,实际上 clj 当年怒艹标算得到了更优的复杂度
感谢 xiaoyimi 大爷帮我优化常数
不多说,这题在 bz 上过不去感动的我热泪盈眶。。。
UPD:将递归改为非递归,加了些小优化终于在 bz 上卡过去。。。感人至深
代码:

#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#define C 60
#define lim 5000000
using namespace std;
typedef long long LL;
LL S,Q,c,n,s,ans,f[lim + 2],g[lim + 2];
char *cp=(char *)malloc(10000000);
void in(LL &x)
{
    for (;*cp<'0'||*cp>'9';++cp);
    for (x=0;*cp>='0'&&*cp<='9';++cp) x=x*10+(*cp)-48;
}
inline void out(LL x)
{
    if (x<0) {putchar('-'),out(-x);return ;}
    if (!x) return;
    out(x/10);
    putchar(x%10+48);
}

inline LL get_point(LL x){
    //if (x == 1) return 1;
    bool v = 1;
    while (x > lim){
    if (!(x & 1)) v ^= 1,x >>= 1;
    else if ((x >> 1)&1) v ^= 1,x = (x + 1)>>1;
    else x = (x + 1)>>1; 
    }
    if (!v) return (-f[x]);
    return f[x];
}

inline LL get_interval(LL l,LL r){
    LL sum = 1;
    while (r - l > 1){
    if (r <= lim) return (sum * (g[r] - g[l - 1]));
    sum <<= 1;
    l = (l + 3)>>2; r >>= 2; }
    if (r - l + 1 == 1) return get_point(l) * sum;
    return (get_point(l) + get_point(r)) * sum;
    //if (r - l + 1 == 1) return get_point(l);
    //if (r - l + 1 == 2) return (get_point(l) + get_point(r));
    //return (get_interval((l + 3) >> 2,r >> 2) << 1);
}

inline void get_init(){
    f[1] = 1;
    for (register int i = 2;i <= lim; ++i)
      if (!(i & 1)) f[i] = -f[i >> 1];
      else  if ((i >> 1)&1) f[i] = -f[(i + 1) >> 1];
      else f[i] = f[(i + 1) >> 1];
    g[0] = 0;
    for (register int i = 1;i <= lim; ++i) g[i] = g[i - 1] + f[i];
}

int main(){
    //cout<<get_point(3)<<endl; 
    //cout<<get_point(2)<<endl;
    //freopen("123.txt","r",stdin);
    fread(cp,1,10000000,stdin);
    get_init();
    in(Q);
    while (Q--){
       in(c);in(n);
       if (c == 1)
       {
        ans=get_point(n);
        if (!ans) putchar('0');
        else out(ans);
       }
       else{
       S = 0; s = 0;
       //for (i = 0;i <= C; ++i)
         //if ((1LL << i) >= n) break;
        //i = min(i,(LL)C); 
       //dfs(1,(1LL << i),1,n);
       for (register LL i = C;i >= 0; --i)
         if ((1LL << i) & n){
            S += get_interval(s + 1,s + (1LL << i));
            s += (1LL << i);
         }
       if (c == 2) {
         if (!S) putchar('0');
         else if (S > 0) putchar('+');
         else putchar('-');
       }
       else if (c == 3) 
       {
         ans=S;
         if (!ans) putchar('0');
         else out(ans);
       }
    }
     puts("");
}
    return 0;
} 

总结:1.二进制相关:二进制分解,线段树,树状数组 (lowbit) ,倍增等等,多想一想
2.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值