题目描述
牛牛有一颗大小为n的神奇 L i n k − C u t Link-Cut Link−Cut 数组,数组上的每一个节点都有两种状态,一种为 l i n k link link 状态,另一种为 c u t cut cut 状态。数组上任意一对处于 l i n k link link 状态的无序点对(即 ( u , v ) (u,v) (u,v) 和 ( v , u ) (v,u) (v,u) 被认为是同一对)会产生 d i s ( u , v ) dis(u,v) dis(u,v) 的 l i n k link link 能量, d i s ( u , v ) dis(u,v) dis(u,v)为数组上u到v的距离。
我们定义整个数组的 L i n k Link Link 能量为所有处于 l i n k link link 状态的节点产生的 l i n k link link 能量之和。
一开始数组上每个节点的状态将由一个长度大小为 n n n 的 01 串 01串 01串 给出, ′ 1 ′ 表 示 L i n k 状 态 '1'表示Link状态 ′1′表示Link状态, ′ 0 ′ 表 示 C u t 状 态 '0'表示Cut状态 ′0′表示Cut状态。
牛牛想要知道整个数组的 L i n k Link Link 能量,为了避免这个数字过于庞大,你只用输出答案对 1 0 9 + 7 10^9+7 109+7取余后的结果即可。
输入描述:
第一行输入一个正整数 n ( 1 ≤ n ≤ 1 0 5 ) n(1 \leq n \leq 10^5) n(1≤n≤105)
接下里一行输入一个长度大小为n的01串表示数组的初始状态,'1’表示Link状态,'0’表示Cut状态。
输出描述:
仅一行,表示整个数组的Link能量对 1 0 9 + 7 10^9+7 109+7 取余后的
输入
3
101
5
00110
6
000010
输出
2
1
0
题解1 ( 直 接 计 算 ) (直接计算) (直接计算)
- 记 录 c n t 与 n u m , c n t 表 示 当 前 经 过 了 多 少 个 1 , n u m 表 示 前 面 所 有 的 点 到 当 前 位 置 的 贡 献 。 记录cnt与num,cnt表示当前经过了多少个1,num表示前面所有的点到当前位置的贡献。 记录cnt与num,cnt表示当前经过了多少个1,num表示前面所有的点到当前位置的贡献。
- 这 个 也 是 最 直 观 最 好 想 到 , 最 有 效 的 算 法 。 这个也是最直观最好想到,最有效的算法。 这个也是最直观最好想到,最有效的算法。
- 因 为 他 时 间 复 杂 度 为 O ( n ) , 空 间 复 杂 度 甚 至 可 以 O ( 1 ) 因为他时间复杂度为O\left(n\right),空间复杂度甚至可以O\left( 1\right) 因为他时间复杂度为O(n),空间复杂度甚至可以O(1)
题解2 ( 分 治 ) (分治) (分治)
-
分治算法计算贡献,对于每一个区间 [ l , r ] \left[ l,r \right] [l,r],它的中点为 m i d mid mid ,该区间的贡献可以分解为以下三部分
- 左侧区间产生的贡献。
- 右侧区间产生的贡献。
- 过中点 m i d mid mid 产生的新贡献。
-
那么对于1,2,我们可以使用递归求解,由于区间越分越小,所以最终复杂度总和为 O ( n l o g 2 n ) O\left(nlog_2n\right) O(nlog2n)
-
考虑3,只需要暴力for左侧区间,统计左侧区间"1"的数目,以及它们到中点的和。
时空复杂度为 O ( n l o g 2 n ) O\left(nlog_2n\right) O(nlog2n) -
这个方法虽然不够优秀,但是它可以扩展到线段树上面维护动态问题(也就是解决第二问),线段树其实就是保留了每次分治的结果。
-
如果学分治的话建议写一写。
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;
}