题目描述
链接:https://ac.nowcoder.com/acm/contest/3004/F
来源:牛客网
题目描述
牛牛有一颗大小为n的神奇Link-Cut 数组,数组上的每一个节点都有两种状态,一种为link状态,另一种为cut状态。数组上任意一对处于link状态的无序点对(即(u,v)和(v,u)被认为是同一对)会产生dis(u,v)的link能量,dis(u,v)为数组上u到v的距离。
我们定义整个数组的Link能量为所有处于link状态的节点产生的link能量之和。
一开始数组上每个节点的状态将由一个长度大小为n的01串给出,'1’表示Link状态,'0’表示Cut状态。
牛牛想要知道整个数组的Link能量,为了避免这个数字过于庞大,你只用输出答案对109+710^9+7109+7取余后的结果即可。
输入描述:
第一行输入一个正整数n(1≤n≤105)(1 \leq n \leq 10^5)(1≤n≤105)
接下里一行输入一个长度大小为n的01串表示数组的初始状态,'1’表示Link状态,'0’表示Cut状态。
输出描述:
仅一行,表示整个数组的Link能量对109+710^9+7109+7取余后的结果。
示例1
输入
3
101
输出
2
示例2
输入
5
00110
输出
1
示例3
输入
6
000010
输出
0
解法一 前缀和
前缀和的思路,但是要用到两次前缀和。首先,先把字符型数组ch中的字符传入长整型数组em中,然后,对em进行2次前缀和处理。
不知道为啥要进行2次前缀和处理的,可以解除“/* */”封印,看一下数据:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod=1e9+7;
int n;
char ch[100010];
ll em[100010];
int main(){
ll um=0;
scanf("%d",&n);
scanf("%s",ch);
for(int i=0;ch[i];i++){
if(ch[i]=='1'){
em[i+1]++;//转换字符
}
}
/*for(int i=1;i<=n;i++){
printf("%lld ",em[i]);
}
printf("\n");*/
for(int i=1;i<=n;i++){
em[i]+=em[i-1]; //前缀和
em[i]%=mod;
}
/*for(int i=1;i<=n;i++){
printf("%lld ",em[i]);
}
printf("\n");*/
for(int i=1;i<=n;i++){
em[i]+=em[i-1]; //前缀和
em[i]%=mod;
}
/*for(int i=1;i<=n;i++){
printf("%lld ",em[i]);
}
printf("\n");*/
for(int i=1;i<=n;i++){ //ch[0]不用计入
if(ch[i]=='1'){
um+=em[i];
um%=mod;
}
}
printf("%lld\n",um);
return 0;
}
解法二
解法二更容易想到,并且空间复杂度比较低,时间:O(n),空间:O(1)。
先把ch中为‘1’的位置记录至em中,例如:em[5]={1,3,5,6,7}:
所以,就变成了只要知道前后两个的位置差距就行了!
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod=1e9+7;
ll n;
char ch[100010];
ll em[100010];
int main(){
scanf("%lld",&n);
ll jj=0;
scanf("%s",ch);
for(int i=0;i<n;i++){
if(ch[i]=='1'){
em[jj++]=i;
}
}
ll sum=0;
for(int i=1;i<jj;i++){
sum=sum%mod+((em[i]-em[i-1])*i*(jj-i))%mod;
}
printf("%lld\n",sum);
return 0;
}