D.Bingbong的奇偶世界
思路1:数学计数
1、对于每一个偶数,那么以它结尾的数都是偶数,这样的数有多少,其实就是前i-1的数的组合:
对于前i-1个数,我们每次可以选0~i-1数,然后求和:C(i-1, 0) + C(i-1, 1) + C(i-1, 2) + .... + C(i-1, i-2) + C(i-1, i-1) == 2^(i-1);
2、因为题目要求不含前导0,所以我们要把含有前导0的组合减掉,经过发现我们可以得知:
对于每一个0 和它后面的偶数x,含有前导零的个数为: 假设0 和 x之间有n个数,sum = C(n,0) + C(n,1) + ... + C(n, n-1) + C(n,n) == 2^n;
3、问题在于如何快速的求得每个0 和 后面偶数的 sum,暴力枚举肯定超时的
4、我们可以找一下规律,看看能否在过程中就求出每个偶数的sum
5、注意点:用快速幂求2^n
#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7;
typedef long long LL;
int quick_pow(int a, int k)
{
int res = 1 % mod;
while(k)
{
if(k & 1) res = (LL)res * a % mod;
a = (LL)a * a % mod;
k = k >> 1;
}
return res % mod;
}
int main()
{
//如何快速去重
//cout << quick_pow(2, 10) << endl;
int n;
string s;
cin >> n >> s;
s = " " + s;
LL res = 0, sum = 0;
for(int i = 1; i <= n; i ++)
{
int num = s[i] - '0';
if(num % 2 == 0)
{
int t = quick_pow(2,i - 1);
//cout << t << endl;
res = (res + t - sum + mod) % mod;//+ mod 防止变成负数
}
sum = (LL)(sum * 2) % mod;
if(num == 0)
{
sum ++;
}
}
cout << res << endl;
return 0;
}
思路2:dp的思想
1、我们记录前i个数的中偶数的个数ans和前i个数可以组成的合法数的个数cnt
2、如果s[i] 是奇数,ans(i) = ans(i-1), cnt = cnt * 2 + 1
cnt的算法,前i-1个数可以组成cnt个合法的数,每个组合加上s[i]可以变成新的数相当于扩大了一倍,再加上s[i] 本身。
3、如果s[i] 是偶数,ans(i) = ans(i-1) + cnt + 1, cnt = cnt * 2 + 1
ans 的算法,前i-1个数中组成的偶数,末尾加上一个偶数还是偶数,所以会新增加cnt + 1(s[i]本身)个偶数
4、如果s[i] 是0,特殊情况,考虑前导0问题,ans(i) = ans(i-1) + cnt + 1, cnt = cnt * 2
,s[i]自己不能作为一个合法的前导数所以没有加1
#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7;
typedef long long LL;
int main()
{
//如何快速去重
int n;
string s;
cin >> n >> s;
s = " " + s;
LL ans = 0, cnt = 0;
for(int i = 1; i <= n; i ++)
{
if(s[i] == '0')
{
ans = (ans + cnt + 1) % mod;
cnt = cnt * 2 % mod;
}
else if((s[i] - '0') % 2 == 1)
{
cnt = (cnt * 2 + 1) % mod;
}
else
{
ans = (ans + cnt + 1) % mod;
cnt = (cnt * 2 + 1) % mod;
}
}
cout << ans << endl;
return 0;
}
E Bingbong的字符串世界
思路:根据题解说是序列自动机问题
1、我们用next[i][j] 维护前i个字符中字符j第一次出现的位置,因为我们要维护第一次出现的位置,所以我们要从后向前遍历,没出现的字符我们标记为0。
2、我们通过find_x(int l, string aim) 来查找从l开始,第一次找到aim序列的位置。
3、最后我们进行子串的统计:r1 表示ac第一次出现的位置,r2表示wa第一次出出现的位置
(1) 如果r1 等于0,表示ac没有出现过,不符合题意。
(2)因为字符串的长度要大于等于k,所以右端点你至少为l+k-1,所以t = max(r1, l+k+-1),当l+K-1 大于n时也是不符合题意的。
(3)当r2 位于 r1 的右边,说明wa在字符串的最后,只要我们不包含wa的最后一个字符就是合法的,合法的字符个数应该为r2 - t。
(4)当r2 ==0时,说明字符串里面没有wa,这种字符串都是符合题意的。
总结: 本题的要点就是如何构造出next[i][j] 和 find_x()函数
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
typedef long long LL;
string s;
int n, k;
int nnext[N][27];
int find_x(int l, string aim)
{
int ff = 1;
for(int j = 0; j < (int)aim.size(); j ++)
{
if(ff) l = nnext[l][aim[j] - 'A'];
else l = nnext[l + 1][aim[j] - 'A'];
ff = 0;
if(l == 0) return l;
}
return l;
}
int main()
{
cin >> n >> k >> s;
s = " " + s;
for(int i = n; i >= 1; i --)
{
for(int j = 0; j <= 25; j ++)
{
nnext[i][j] = nnext[i+1][j];
}
nnext[i][s[i] - 'A'] = i;
}
string ac = "ACCEPT", wa = "WA";
LL res = 0;
for(int i = 1; i <= n; i ++)
{
int r1 = find_x(i, ac), r2 = find_x(i, wa);
if(r1 == 0) continue;
if(i + k - 1 > n) continue;
int t = max(r1, i + k - 1);
if(r2 >= t)
{
res += r2 - t;
}
else if(r2 == 0)
{
res += n - t +1;
}
}
cout << res << endl;
return 0;
}
F Bingbong的幻想世界
思路:位运算
1、首先我们考虑,每一个数a[i],我们可以考虑它对二进制位上的贡献。
2、考虑二进制数枚举到t位,数组枚举到第i个:
(1)当a[i]二进制第t位是1的时候,我们可以得出在[1, i-1]区间中在二进制的第t位上是0的数所贡献的区间的个数为v0个,a[i]后面的区间为(n-i+1)个,所以贡献即为v0 * (n-i+1) * (1 << t),又因为一个区间需要两两异或运算,所以对于一对a[i]和a[j]需要计算两遍,故总贡献v0 * (n-i+1) * (1 << t) * 2
(2)对于是1的时候同理
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
const int mod = 1e9 + 7;
typedef long long LL;
int a[N];
int main()
{
int n;
cin >> n;
for(int i = 1; i <= n; i ++)
{
cin >> a[i];
}
int v1 = 0, v0 = 0, res = 0;
for(int t = 0; t <= 20; t ++)
{
int v0 = 0, v1 = 0;
for(int i = 1; i <= n; i ++)
{
if((a[i] >> t) & 1)
{
res = res + (LL)v0 * (n - i + 1) % mod * (1 << t) % mod;
res %= mod;
v1 = (v1 + i) % mod;
}
else
{
res = res + (LL)v1 * (n - i + 1) % mod * (1 << t) % mod;
res %= mod;
v0 = (v0 + i) % mod;
}
}
}
res %= mod;
cout << res * 2 % mod <<endl;
return 0;
}