P3604 美好的每一天

记得不久前畜牧给我口糊了一道题目。给定一棵树,每条边有一个权值,求所有路径上边的异或和的和。做法是用f[i][j][0]、f[i][j][1]表示第i个点,子树中路径的异或和的第j个二进制位为0、1的个数,然后大力dp就可以了。

看到这题的第一反应是用O(knlogn)的线段树合并。再加上这是一道关于异或和的题目,很容易就和畜牧说的题联系在了一起,然后就能把这题A掉了——吗?

显然不是,应该是然后我拼命的往这个方向思考,荒废了一下午的时光。

上面的好像全是废话,没错,所以它们被我划掉了。这个故事告诉我们划掉的话就不要去看了233(包括这句话)。

题解告诉我们,这题是道莫队题。(讲道理做这道题前我根本没听说过这个算法)

膜队要求能从[l,r]快速转移到[l,r+1](这里只举这个例子)。蒟蒻表示懵逼。蒟蒻不知道如何取出一段区间的所有后缀区间。膜了数小时的题解后,后知后觉的蒟蒻才知道:

先设这段后缀区间异或和为S。

整段区间的异或和可以用前缀和预处理。设为a。

用d表示S对应的前缀(从第一个数开始),使d^S=a。

符合条件的区间满足S=1<<i,(-1≤i≤26)

<=>d^a=1<<i

<=>d=a^1<<i

这样子枚举就容易了。

然而我的代码自带神秘常数,怎么卡都只有70分。于是决定假装已经A过这题了。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
#define rep(i,j,k) for(i=j;i<=k;++i)
#define per(i,j,k) for(i=j;i>=k;--i)
#define ll long long
#define us unsigned short
#define db double
#define ldb long double
#define pii pair<int,int>
#define mkp make_pair
#define X first
#define Y second
const int N=60005;
pii qu[N];char str[N];
int n,m,s,t,sa[N],mo[N],a[N],S[N],sum,ans[N];us cnt[70000000];
bool cmp(int x,int y){
    return mo[qu[x].X]==mo[qu[y].X]?qu[x].Y<qu[y].Y:qu[x].X<qu[y].X;
}
void add(int x){
    int i;
    sum+=cnt[x];
    rep(i,0,25)sum+=cnt[x^1<<i];
    ++cnt[x];
}
void sub(int x){
    int i;
    --cnt[x];
    sum-=cnt[x];
    rep(i,0,25)sum-=cnt[x^1<<i];
}
int main(){
    int i,x,y,u,v;
    scanf("%d%d%s",&n,&m,str);s=sqrt(n)+(n==1);
    rep(i,1,n){
        a[i]=1<<str[i-1]-'a';
        S[i]=S[i-1]^a[i];
        mo[i]=t+=i%s==1;
    }
    rep(i,1,m){
        scanf("%d%d",&x,&y);
        qu[sa[i]=i]=mkp(x,y);
    }
    sort(sa+1,sa+m+1,cmp);
    x=1,y=1;cnt[0]=1;add(S[1]);
    rep(i,1,m){
        u=qu[sa[i]].X,v=qu[sa[i]].Y;
        for(;y<v;add(S[++y]));
        for(;y>v;sub(S[y--]));
        for(;x>u;add(S[(--x)-1]));
        for(;x<u;sub(S[(x++)-1]));
        ans[sa[i]]=sum;
    }
    rep(i,1,m)printf("%d\n",ans[i]);
     return 0;
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值