1054D Changing Array 【位运算+思维】

题目:戳这里

题意:两个数n,k,满足给的n个数小于2的k次方。每个数可以进行一次操作,即把a[i]换成a[i]^(1<<k-1);求最多的连续区间数使得 区间[L,R] (1<=L<=R<=n),满足: a[L] ^ a[L+1] ^ … ^ a[R-1] ^ a[R] != 0

解题思路:

首先我们知道n个数可构成的连续区间是n*(n+1)/2个,如果一个一个找肯定会超时,需要一种能快速算出[L,R]区间异或和的方法。因为异或满足交换法则。所以a[1]^a[2]^...^a[L-1] ^ a[1]^a[2]^...^a[R]=a[L]^a[L+1]^...^a[R];也就是只要纪录前缀异或和,就可以快速算出每个区间的异或值。

设前缀异或和为s[],maxx=1<<k-1。

因为相等的值相互异或为0,所以只要s[L]==s[R],则[L,R]区间的异或和为0. 此外,假设对a[i]进行一次异或操作得a[i]'=a[i]^maxx,则s[i]'=s[i]^maxx,因为异或满足s[i]^maxx^maxx=s[i],所以对于s[]数组,每进行两次^maxx的操作,当前的前缀值s[i]就相当于没有进行操作。

这样问题就转化为,经过无数次操作后,使得s[]数组中相等的数尽量的少,就可以用贪心解决了。

用map<ll,ll>mp维护每个值出现的次数。计算前缀和s[i]值时,决策s[i]=mp[s[i-1]]>mp[s[i-1]^maxx]?s[i-1]^maxx:mp[s[i-1]];用基础的组合知识(确定L,R两端点便可以确定一个区间)就可以算出最终答案。

 

附ac代码:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 typedef long long ll;
 4 const int maxn = 2e5 + 10;
 5 const ll mod = 998244353;
 6 ll a[maxn];
 7 int main() {
 8 
 9     ll n, k;
10     scanf("%lld %lld", &n, &k);
11     for(int i = 1; i <= n; ++i) {
12         scanf("%lld", &a[i]);
13     }
14     ll ans = ll((n + 1) * n / 2);
15     ll maxx = 1ll;
16     maxx = (maxx << k) - 1ll;
17     map<ll,ll> mp;
18     mp[0] = 1;
19     ll now = 0;
20     for(ll i = 1; i <= n; ++i) {
21         now ^= a[i];
22         now = mp[now]>mp[now ^ maxx]?now^maxx:now;
23         ans -= mp[now];//组合数Cn2
24         mp[now]++;
25     }
26     printf("%lld\n", ans);
27     return 0;
28 }
View Code

 

posted @ 2018-11-05 16:13  euzmin  阅读(221)  评论(0编辑  收藏  举报