codeforce gym 100307H Hack Protection

原题地址:http://codeforces.com/gym/100307/problem/H

题意:

给定一个序列,求序列的子区间中,满足子区间XOR值等于AND值得子区间个数。

题解:

一直以为NEERC这种有名的比赛应该题解到处都是,太天真了……

首先考虑区间的AND值。

对于固定起点的区间,因为 & 的性质,AND值必然单调递减,并且,根据AND值中1的个数划分后面的区间,必然最多只能划分成32段,对于某一段中的任意位置,它和固定的起点所形成的子区间的AND值是一样的。

再来考虑区间的XOR值

对于数列A[],定义sum(a,b)=A[a]^A[a+1]^...^A[b];

那么sum(a,b)=(A[1]^A[2]^...^A[a-1])^((A[1]^A[2]^...^A[a-1])^(A[a]^A[a+1]^...^A[b])=sum(1,a-1)^sum(1,b),这是一个前缀和形式

考虑题给的等式,AND(a,b)==XOR(a,b)

也就是AND(a,b)==XOR(1,a-1)^XOR(1,b)

也就是AND(a,b)^XOR(1,a-1)==XOR(1,b) ——(1)

我们枚举区间的起点,XOR(1,a-1)是定值,接下来枚举AND值相同的区段,最多32段,在每一段中寻找满足等式(1)的b,累计答案

区间AND值可以用RMQ思想处理,区段的划分可以二分处理,而寻找区段中XOR值为固定值的元素个数,可以转化为前缀和

#include<bits/stdc++.h>

#define clr(x,y) memset((x),(y),sizeof(x))

using namespace std;
typedef long long LL;

struct seg
{
    int l;
    int val;
};

const int maxn=1e5;

int AND[maxn+5][18];
int XOR[maxn+5];
int n;
int A[maxn+5];
vector <seg> v[maxn+5]; //段划分
map <int,vector<int> > mp; //mp[x]中储存XOR[1,a]==x的元素下标a

void build() //计算区间AND值
{
    for (int i=1;i<=n;++i)
        AND[i][0]=A[i];

    for (int j=1;(1<<j)<=n;++j)
    {
        for (int i=0;i+(1<<j)-1<=n;++i)
        {
            AND[i][j]=AND[i][j-1] & AND[i+(1<<(j-1))][j-1];
        }
    }
}

int calu(int l,int r)
{
    int k=0;
    while ((1<<(k+1))<=r-l+1) ++k;
    return AND[l][k] & AND[r-(1<<k)+1][k];
}

LL Count(int l,int r,int val) //区间[l,r]中XOR值为val的元素个数
{
    return upper_bound(mp[val].begin(),mp[val].end(),r)-lower_bound(mp[val].begin(),mp[val].end(),l);
}

void solve()
{
    ++n;
    A[n]=0;//为了便于划分在末尾加上一个0

    build();
    for (int i=1;i<=n;++i) v[i].clear();

    for (int i=1;i<=n;++i)
    {
        int val=A[i];
        int l=i;
        int r=n;

        v[i].push_back((seg){l,val});
        while (val!=0) //不断二分划分段
        {
            int lb=l;
            int ub=r;
            while (ub-lb>1)
            {
                int mid=(ub+lb)>>1;
                if (calu(i,mid)==val)
                {
                    lb=mid;
                }
                else
                {
                    ub=mid;
                }
            }
            l=ub;
            val=calu(i,l);

            v[i].push_back((seg){l,val});
        }
    }

    mp.clear();
    XOR[0]=0;
    for (int i=1;i<=n;++i)
    {
        XOR[i]=XOR[i-1]^A[i];
        mp[XOR[i]].push_back(i);
    }

    LL ans=0;
    for (int i=1;i<=n;++i)
    {
        for (int j=0;j<v[i].size();++j)
        {
            int l=v[i][j].l;
            int r=(j==v[i].size()-1)? n:v[i][j+1].l-1;
            int val=v[i][j].val^XOR[i-1];

            ans+=Count(l,r,val);
            //printf("%d %d %d\n",l,r,val);
        }
    }

    //消除最后那个虚拟的0对答案的影响
    LL extra=1;
    int xor_tmp=0;
    for (int i=n-1;i>=1;--i)
    {
        xor_tmp^=A[i];
        if (xor_tmp==0) ++extra;
    }
    printf("%lld\n",ans-extra);
}

int main(void)
{
    #ifdef ex
    freopen ("../in.txt","r",stdin);
    //freopen ("../out.txt","w",stdout);
    #endif

    freopen("hack.in","r",stdin);
    freopen("hack.out","w",stdout);

    while (scanf("%d",&n)==1)
    {
        for (int i=1;i<=n;++i)
        {
            scanf("%d",&A[i]);
        }
        solve();
    }
}
时间: 2024-11-05 22:04:29

codeforce gym 100307H Hack Protection的相关文章

(ST表+二分+前缀和)CSU 1879 - Hack Protection

题意: 给定一个序列,求异或和与按位与和相同的区间有几个. 异或和:n个数异或起来.按位与和类似. 分析: 这才是神题,基础算法大杂烩. 问大佬这题的时候,人家只说很不难啊.. 只能说自己太菜. 由于询问区间个数,自然要快速知道某一个区间的异或和与按位与和. 异或和很简单,利用他的性质,直接求前缀和即可. 但是按位与没有和加法和异或类似的性质,无法直接求出. 但是利用之前的知识可以将所有结果预处理出来,而且只要nlogn的复杂度. 就是之前搞RMQ的ST表,很适合离线查询. 几乎不用动的把 mi

codeforce Gym 100418K Cards (概率,数学)

题意:麦田的故事,n张牌,取x张牌,记住前x张牌最大的值m,继续往后取,遇到第一张比m大的牌就停下来.求一个x使得最后的牌在整副牌里是最大的期望最大. 假设最大的牌是A,A在各种位置出现的概率就是相等的,在A固定的情况下,在它前面的牌中最大的牌B,出现在各个位置的概率也是相等的.所以就是要求一个X,使得 下面这个矩形框中的概率和最大. 样例,n=5 #include<bits/stdc++.h> int main() { int n;scanf("%d",&n);

codeforce Gym 100685E Epic Fail of a Genie(贪心)

题意:给出一堆元素,求一个子集,使子集的乘积最大,如有多个,应该使子集元素个数尽量小. 题解:贪心,如果有乘积大于1的正数,那么是一定要选的,注意负数也可能凑出大于1的正数,那么将绝对值大于1的负数两两配对,如果还剩下一个,那么在判断一下,那个负数和比它小的最小负数的乘积是否大于1,如果是那么就选这两个.把所有可能凑成大于1的数选完以后,剩下的数一定比1小,那么就不选. 如果无法凑出大于1的数,那么再分类讨论一下. 挺容易写错... #include<bits/stdc++.h> using

codeforce gym 100548H The Problem to Make You Happy

题意: Alice和Bob在一个有向图上玩游戏,每个人各自操作一个棋子,如果两个棋子走到一个点上,判定Bob输:如果轮到任何一方走时,无法移动棋子,判定该方输 现在Bob先走,要求判断胜负 题解 模型上看是SG问题,但是通常的SG做法需要DP,但是考虑这不是DAG模型,普通的记忆化搜索写法会RE 正解的DP做法:dp[i][j][k]:i,j是Bob,Alice的位置,k是目前轮到谁走了. 开始将所有显然的Bob输的情况加入队列中,不断拓展,找到所有的Bob输的情况. 转移类似SG #inclu

codeforce Gym 101102A Coins (01背包变形)

01背包变形,注意dp过程的时候就需要取膜,否则会出错. 代码如下: #include<iostream> #include<cstdio> #include<cstring> using namespace std; #define MAXW 15005 #define N 155 #define LL long long #define MOD 1000000007 int w1[N],w2[N]; LL dp1[MAXW],dp2[MAXW]; int main(

codeforce Gym 100342J Triatrip (bitset)

傻逼题,但是为什么别人的O(n^3)不会T?只是因为用了bitset优化... 附上一张bitset基本操作的表 #include<bits/stdc++.h> using namespace std; const int maxn = 1500+2; char g[maxn][maxn]; bitset<maxn> b1[maxn],b2[maxn],res; #define local int main() { #ifdef local freopen("triatr

codeforce Gym 100203I I WIN (网络流)

把'I'拆成容量为1一条边,一个入点一个出点,入点和相邻的'W'连一条容量为1的边,出点和相邻的'N'连一条容量为1,所有的'W'和源点连一条容量为1边,所有的'N'和汇点连一条容量为1的边,表示只能用一次.一发网络流就过了. 写了4000B+的贪心,然并卵 #include<bits/stdc++.h> using namespace std; const int INF = 0x3fffffff; const int maxn = 2142; #define PB push_back st

codeforce Gym 100342H Hard Test (思考题)

题意:构造让Dijkstra单源最短路算法有效松弛次数最多的数据... 题解:构造,题意换种说法就是更新晚的路径要比更新早的路径短.因为所有点都会更新一次,那么按照更新时间形成一条链,即到最后一个点的最短路径, 注意:越在这条链的后面的边越晚更新,然后添加边,在前面的点所连的边一定是先更新的,所以反过来添加边的时候只要保证比之前的路径更长就行了. #include<bits/stdc++.h> using namespace std; typedef long long ll; #define

codeforce Gym 100685F Flood

一开始T了,因为某些个结点被重复扩展了多次,科学做法是topo排序,每次只把入度为0的点放入队列,这样就严格保证了每个结点只被扩展一次. #include<bits/stdc++.h> using namespace std; #define eps 1e-9 #define bug(x) cout<<#x<<'='<<x<<endl; #define Min 1e-7 const int maxn = 1e4+4; const int maxe