2020牛客寒假算法基础集训营3——F.牛牛的Link Power I【计算】【分治】

这篇博客介绍了如何解决一个关于Link-Cut数组的算法问题,其中数组节点有Link和Cut两种状态,Link状态的节点对会产生能量。题目要求计算整个数组的Link能量并对其取模。博主提供了两种解决方案:直接计算和分治算法,分别分析了它们的时间复杂度和空间复杂度,并给出了AC代码示例。
摘要由CSDN通过智能技术生成

题目传送门


题目描述

牛牛有一颗大小为n的神奇 L i n k − C u t Link-Cut LinkCut 数组,数组上的每一个节点都有两种状态,一种为 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状态 1Link ′ 0 ′ 表 示 C u t 状 态 '0'表示Cut状态 0Cut

牛牛想要知道整个数组的 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(1n105)

接下里一行输入一个长度大小为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表示前面所有的点到当前位置的贡献。 cntnumcnt1,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 ,该区间的贡献可以分解为以下三部分

    1. 左侧区间产生的贡献。
    2. 右侧区间产生的贡献。
    3. 过中点 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值