2020安徽省大学生程序设计大赛题解——D 字符串修改

2020安徽省大学生程序设计大赛题解——D 字符串修改

D 字符串修改

对于两个由0与1组成的长度均为N的序列S和T,f (S,T)定义如下:
重复如下操作,使得序列S与序列T相同。f (S,T)是经过操作后,所需要的最少的花费。
改变S[i]的值(O改为1或者1改为0),它的花费为DC[i],其中D是在此操作之前,序列中满足S[j]≠T[j]的j的个数(1K<=j<=N)。C[i]表示对i位置进行变换的代价。
对于一个固定长度N,共有2N
(2N-1)对由01组成的序列(S,T),计算所有f(S,T)的总和,并输出它对10+7的结果。

题目

在这里插入图片描述

标签

快速读入、快速幂

分析

本题的第一个难点在于理解题意,想要理解题意,首先要知道题目中这句话的描述是什么意思——

对于一个固定长度   N,共有 2 N × ( 2 N − 1 ) 对由   01   组成的序列(S,T) \textit{对于一个固定长度 N,共有$2^N \times (2^N-1)$对由 01 组成的序列(S,T)} 对于一个固定长度 N,共有2N×(2N1)对由 01 组成的序列(S,T)

N = 2 N=2 N=2为例,则 0 0 0 1 1 1可以组成 00 00 00 01 01 01 10 10 10 11 11 11四种 01 01 01对,这四种 01 01 01对有 A 4 2 A_4^2 A42种对应方式,如下图所示。
图D-1
图 D − 1      对 于 样 例 数 据 二 的 展 示 图D-1 \ \ \ \ 对于样例数据二的展示 D1    

由此可以推导出,长度为 N N N的子串,可以组成 2 N 2^N 2N 01 01 01对,这些 01 01 01对有 A 2 N 2 A_{2^N}^2 A2N2种对应方式。充分理解这句话,是解决此问题的必要条件。

如果分析这么多种对应方式,那显然是一项庞大的工作,因此我们考虑对这个问题进行简化。

简化的切入点是不同对应方式的异同点上,不难发现,每一个 f ( S , T ) f(S,T) f(S,T),其花费 c o s t cost cost只和 S S S T T T两子串对等位置上不同的元素数量有关。例如 1001 1001 1001 0111 0111 0111,有 [ 0 ] [0] [0] [ 1 ] [1] [1] [ 2 ] [2] [2]三个对等位置的元素不同。我们定义 S S S T T T对等位置的不同元素数量为 x x x,则每一对 S S S T T T都对应一个 c o s t ( x ) cost(x) cost(x)

图D-2
图 D − 2      对 c o s t ( x ) 的 展 示 图D-2 \ \ \ \ 对cost(x)的展示 D2    cost(x)

基于此,我们把问题简化为求解所有 f ( S , T ) f(S,T) f(S,T)对应的 c o s t ( x ) cost(x) cost(x)。换言之,本题的求解被拆分为了两个子问题:

子问题一:对于给定的 f ( S , T ) f(S,T) f(S,T),确定最小的 c o s t ( x ) 对 应 的 方 案 cost(x)对应的方案 cost(x)

我们以 N = 3 N=3 N=3为例,只需要讨论一种 S S S对应的所有 T T T的情况,即可推出所有的 f ( S , T ) f(S,T) f(S,T),因为这样的映射关系存在对称性。当 N = 3 N=3 N=3时,取 S = 000 S=000 S=000,并用橙色表示 x = 1 x=1 x=1 f ( S , T ) f(S,T) f(S,T),用蓝色表示 x = 2 x=2 x=2,绿色表示 x = 3 x=3 x=3的情况如下图所示。

图D-3
图 D − 3      S = 000 时 , f 的 所 有 情 况 图D-3 \ \ \ \ S=000时,f的所有情况 D3    S=000f

想要让总花费最小,则需要让变换次数较小的位置做变换代价较高的变换,然而,每个坐标的变换代价一致,因此想改变每次变换的花费只能从改变坐标变换的次序入手。

我们以 c o s t ( 3 ) cost(3) cost(3)为例,可以得出 c o s t ( 3 ) = c s t [ 1 ] × 1 + c s t [ 2 ] × 2 + c s t [ 3 ] × 3 cost(3)=cst[1] \times 1+cst[2] \times 2+cst[3] \times 3 cost(3)=cst[1]×1+cst[2]×2+cst[3]×3,其中 c s t [ i ] cst[i] cst[i]是在 f ( S , T ) f(S,T) f(S,T)有i个位置元素不同。很自然的想到,对于每个 f ( S , T ) f(S,T) f(S,T),先让变换代价较小的位置进行变换是最优的解决方案。

因此我们对数组 c c c进行由大到小的排序,考虑到整个问题具有轮换对称性,我们不失一般性的只分析 c [ 0 ] < c [ 1 ] < c [ 2 ] c[0]<c[1]<c[2] c[0]<c[1]<c[2]的情况。

图D-4
图 D − 4      S = 000 时 , 最 优 的 变 换 代 价 图D-4 \ \ \ \ S=000时,最优的变换代价 D4    S=000

上图中,橙色线表示 x = 1 x=1 x=1时每个对应关系的 c o s t cost cost,蓝色、绿色同理。依此,我们得出了每个 f ( S , T ) f(S,T) f(S,T)的最优方案。下面的工作会将其抽象成具体的数学公式。

子问题二:求解 x x x的所有可能取值,记录相应的总花费

x x x的取值范围显然是小于等于 N N N的所有正整数。我们依此分析 x x x的不同取值对应的结果,并探究其规律。

x = 1 x=1 x=1时,显然 c o s t ( 1 ) = Σ c [ i ] cost(1)=\Sigma c[i] cost(1)=Σc[i]
x = 2 x=2 x=2时,图 D − 4 D-4 D4给出了答案—— c [ 0 ] × 4 + c [ 1 ] × 3 + c [ 2 ] × 2 c[0] \times 4 +c[1] \times 3 +c[2] \times 2 c[0]×4+c[1]×3+c[2]×2
x = 3 x=3 x=3时,答案为 c [ 0 ] × 3 + c [ 1 ] × 2 + c [ 2 ] c[0] \times 3 + c[1] \times 2 +c[2] c[0]×3+c[1]×2+c[2]

以上结果相加得 c [ 0 ] × 8 + c [ 1 ] × 6 + c [ 2 ] × 4 c[0] \times 8 + c[1] \times 6 +c[2] \times 4 c[0]×8+c[1]×6+c[2]×4,对应的数学表达式恰好满足 a n s = Σ c [ i ] ∗ ( i + 2 ) ∗ 2 N − 2 ans = \Sigma c[i] * (i + 2) * 2 ^{N-2} ans=Σc[i](i+2)2N2

能够得出这样的表达式,其原因并非一昧的臆测规律。我们以 x = 2 x=2 x=2为例,易证变换代价最大的位置,其每次变换都会在其他位置完成变换之后;相应的,变换代价最小的位置每次变换都在最前。对于确定的 N N N x x x,变换代价最大的位置变换 C N − 1 x − 1 C _{N-1} ^{x-1} CN1x1次,变换代价最小的位置变换 C N − 1 x − 1 + N − 1 C _{N-1} ^{x-1} + N - 1 CN1x1+N1次。即,所有位置按照变换代价从大到小排序,形成从 C N − 1 x − 1 C _{N-1} ^{x-1} CN1x1 C N − 1 x − 1 + N − 1 C _{N-1} ^{x-1} + N - 1 CN1x1+N1的公差为 1 1 1的等差数列。

基于上述规律,我们便可得出一般性规律,即 a n s = Σ c [ i ] ∗ ( i + 2 ) ∗ m i ans = \Sigma c[i] * (i + 2) * mi ans=Σc[i](i+2)mi,其中 m i = 2 N − 2 mi = 2 ^{N-2} mi=2N2

这是单一的 S S S对应的花费, S S S共有 2 N 2^N 2N种取值可能,因此最终结果为 a n s ∗ 2 N ans * 2^N ans2N

问题到这里还没有结束,我们的数据范围较大,用朴素的幂或者内置的 p o w pow pow函数不能计算如此大的数。因此我们引入快速幂算法计算上述公式中出现的幂次的值。

快速幂算法如下:

快速幂的目的是做到快速求幂,假设我们要求 a b a^b ab,按照朴素算法就是把 a a a连乘 b b b次,这样一来时间复杂度是 O ( b ) O(b) O(b)也即是 O ( n ) O(n) O(n)级别,快速幂能做到 O ( l o g n ) O(logn) O(logn),快了很多。

我们都学习过进制与进制的转换,知道一个 b b b进制数的值可以表示为各个数位的值与权值之积的总和。比如, 2 2 2进制数 1001 1001 1001,它的值可以表示为 10 10 10进制的 1 ∗ 2 3 + 0 ∗ 2 2 + 0 ∗ 2 1 + 1 ∗ 2 0 1*2^3 + 0*2^2 + 0*2^1 + 1*2^0 123+022+021+120,即 9 9 9。这完美地符合了上面的要求。可以通过 2 2 2进制来把 n n n转化成 2 k m 2^{km} 2km的序列之和,而 2 2 2进制中第 i i i位(从右边开始计数,值为 1 1 1或是 0 0 0)则标记了对应的 2 ( i − 1 ) 2^(i - 1) 2(i1)是否存在于序列之中。譬如, 13 13 13为二进制的 1101 1101 1101,他可以表示为 2 3 + 2 2 + 2 0 2^3 + 2^2 + 2^0 23+22+20,其中由于第二位为 0 0 0 2 1 2^1 21项被舍去。

如此一来,我们只需要计算 a 、 a 2 、 a 4 、 a 8 . . . . . . a ( 2 k m ) a、a^2、a^4、a^8......a^(2^km) aa2a4a8......a(2km)的值(这个序列中的项不一定都存在)并把它们乘起来即可完成整个幂运算。借助位运算的操作,可以很方便地实现这一算法,其复杂度为 O ( l o g n ) O(logn) O(logn)

typedef long long ll;
const int M = 1e9 + 7;
ll quick_pow(ll x, ll n, ll m = M) {
	ll res = 1;
	while (n > 0) {
		if (n & 1)	res = res * x % m;
		x = x * x % m;
		n >>= 1;
	}
	return res;
}

样例所需读入数据也具有不小的规模,我们引入了快速读入算法。

typedef long long ll;
inline ll read() {
	register ll x = 0, f = 1; char ch = getchar();
	while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getchar(); }
	while (isdigit(ch)) { x = (x << 3) + (x << 1) + ch - '0'; ch = getchar(); }
	return (f == 1) ? x : -x;
}
参考答案(C++)
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int M = 1e9 + 7;

inline ll read() {
	register ll x = 0, f = 1; char ch = getchar();
	while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getchar(); }
	while (isdigit(ch)) { x = (x << 3) + (x << 1) + ch - '0'; ch = getchar(); }
	return (f == 1) ? x : -x;
}

ll quick_pow(ll x, ll n, ll m = M) {
	ll res = 1;
	while (n > 0) {
		if (n & 1)	res = res * x % m;
		x = x * x % m;
		n >>= 1;
	}
	return res;
}

bool cmp(int a, int b) {
	return a > b;
}

ll ans, n;

int main() {
	n = read();
	if (n == 1) {
		ll temp = read();
		cout << temp * 2 % M;
		return 0;
	}
	ll mi = quick_pow(2, n - 2);
	ll* c = new ll[n + 1];
	for (ll i = 0; i < n; i++) {
		c[i] = read();
	}
	sort(c, c + n, cmp);

	for (ll i = 0; i < n; i++) {
		ans += c[i] * (i + 2) * mi % M;
		ans %= M;
	}

	cout << ans * quick_pow(2, n) % M;
}
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值