题目描述
牛牛有一颗大小为n的神奇 数组,数组上的每一个节点都有两种状态,一种为 状态,另一种为 状态。数组上任意一对处于 状态的无序点对(即 和 被认为是同一对)会产生 的 能量, 为数组上u到v的距离。
我们定义整个数组的 能量为所有处于 状态的节点产生的 能量之和。
一开始数组上每个节点的状态将由一个长度大小为 的 给出, , 。
牛牛想要知道整个数组的 能量,为了避免这个数字过于庞大,你只用输出答案对 取余后的结果即可。
输入描述:
第一行输入一个正整数
接下里一行输入一个长度大小为n的01串表示数组的初始状态,'1’表示Link状态,'0’表示Cut状态。
输出描述:
仅一行,表示整个数组的Link能量对 取余后的
输入
3
101
5
00110
6
000010
输出
2
1
0
题解1
题解2
-
分治算法计算贡献,对于每一个区间 ,它的中点为 ,该区间的贡献可以分解为以下三部分
- 左侧区间产生的贡献。
- 右侧区间产生的贡献。
- 过中点 产生的新贡献。
-
那么对于1,2,我们可以使用递归求解,由于区间越分越小,所以最终复杂度总和为
-
考虑3,只需要暴力for左侧区间,统计左侧区间"1"的数目,以及它们到中点的和。
时空复杂度为 -
这个方法虽然不够优秀,但是它可以扩展到线段树上面维护动态问题(也就是解决第二问),线段树其实就是保留了每次分治的结果。
-
如果学分治的话建议写一写。
AC-Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
const int maxn = 1e5 + 7;
const int mod = 1e9 + 7;
int main() {
ios;
int n; string s;
cin >> n >> s;
ll ans = 0;
ll num = 0;
int cnt = 0;
for (int i = 0; i < s.size(); ++i) {
num = (num + cnt) % mod; // 如果前面有cnt个1,那么每个1的位置往当前位置的距离都需要+1
if (s[i] == '1') {
if (cnt)
ans = (ans + num) % mod;
++cnt;
}
}
cout << ans << endl;
}
AC-Code
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
const int mod = 1e9 + 7;
char s[MAXN];
int n;
long long ans;
void div_algorithm(int l, int r) {
if (l == r) return;
int mid = (l + r) >> 1;
long long leftsum = 0;
long long leftcnt = 0;
for (int i = mid; i >= l; --i) {
if (s[i] == '1') {
leftsum = (leftsum + mid - i) % mod;
++leftcnt;
}
}
for (int i = mid + 1; i <= r; ++i) {
if (s[i] == '1') {
ans = (ans + leftsum + (i - mid) * leftcnt % mod) % mod;
}
}
div_algorithm(l, mid); // 计算左侧贡献
div_algorithm(mid + 1, r); // 计算右侧贡献
}
int main()
{
scanf("%d", &n);
scanf("%s", s + 1);
div_algorithm(1, n);
printf("%lld\n", ans);
return 0;
}