洛谷「Daniel13265 的公开赛」A-替换
题目链接:https://www.luogu.com.cn/problem/P6297
时间限制:1.00s | 内存限制:125.00MB
题目背景
替换永远比删除更彻底。
题目描述
Daniel13265 有一串由各种漂亮的贝壳组成的项链,但由于各种原因,这个项链不是环形的,而仅仅是用一根普通的丝线串起来的。项链上的每个贝壳都有一个好看程度 ai,相同种类的贝壳有着相同的好看程度,而不同种类的贝壳有着不同的好看程度。
Danie13265 定义, 第 l 个至第 r 个这一段贝壳是对称的,当且仅当
∑
i
=
l
r
(
a
i
−
a
l
+
r
−
i
)
2
=
0
\sum_{i=l}^r(a_i-a_{l+r-i})^2=0
i=l∑r(ai−al+r−i)2=0
Daniel13265 经常从中取出一段贝壳。如果这一段贝壳是对称的,他就会非常高兴;如果这一段贝壳不是对称的,那么他会将其中的某些贝壳替换成新的,以使得这一段贝壳成为对称的。一次替换可以任意地改变任何一个位置上贝壳的好看程度,但是过多的替换会使这一段贝壳脱离原本的模样,所以 Daniel13265 至多会进行 k 次替换。如果一段贝壳在进行至多 k 次替换后能够成为对称的,那么 Daniel13265 就称这一段贝壳是「可观赏的」。
Daniel13265 简单地将第 l 个至第 r 个这一「可观赏的」的贝壳段的「观赏指数」定义为
∏
i
=
l
r
a
i
\prod_{i=l}^r a_i
i=l∏rai
其中 ai 表示第 i 个贝壳原本的好看程度。
他现在很好奇,在这个贝壳组成的项链中,「可观赏的」贝壳段中「观赏指数」的最大值。但是由于这个值可能很大,所以你只需要求出它对 109+7 取模后的结果即可。
输入格式
输入共 2 行。
第一行两个正整数 n,k,表示这个贝壳组成的项链中贝壳的数目与 Daniel13265 对一段贝壳最多进行替换的次数。
第二行 n 个用单个空格隔开的正整数,第 i 个数 ai表示项链上第 i 个贝壳的好看程度。
输出格式
输出一行一个非负整数,表示「可观赏的」贝壳段中「观赏指数」的最大值对 109+7 取模后的结果。
输入输出样例
输入#1
7 1
1 2 4 2 3 3 4
输出#1
288
输入#2
6 1
3 1 2 250000002 1 2
输出#2
1
说明/提示
样例解释#1
「可观赏的」贝壳段有 [1],[2],[3],[4],[1,2],[2,3],[2,4],[3,3],[3,4],[4,2],[1,2,4],[2,3,3],[2,4,2],[3,3,4],[4,2,3],[2,3,3,4],[4,2,3,3,4],其中「观赏指数」最大的贝壳段为 [4,2,3,3,4]。
样例解释#2
「可观赏的」的贝壳段中「观赏指数」最大的为 [2,250000002,1,2],其值为 109+8,对 109+7 取模后结果为 1。
数据范围
对于 100% 的数据,满足 1 ≤ n ≤ 1000,0 ≤ k ≤ n,1 ≤ ai ≤ 109+7
题意简析
这道题,作为A题,题干着实有点恐怖,简直成了数学小作文阅读理解,刚开始就劝退好多人(包括我)
(后来题面看了一轮,又回来了… )
咳咳,那么这个题干到底讲了啥呢。In short:(写到这里突然尬住,到底讲啥了呢(挠头) )
①有一串由贝壳组成的项链(非环),每个贝壳有一个「好看程度」,ai
②定义,l 至 r 段贝壳是对称的,当且仅当(上面那个和式成立)
我们小学二年级就学过:若平方项的和为0,则每一平方项均为0
即,ai==al+r-i,而 i 从 l 开始递增,也就是说 l~r 段关于中轴对称(物理上的对称)
③一次替换可以改变任意贝壳的「好看程度」,假如一段贝壳经过 k 次替换后,是对称的,
则称 l~r 这一段贝壳为「可观赏的」(有种机翻的赶脚?)
④定义一段「可观赏的」贝壳的「观赏指数」为这一段贝壳「好看程度」的连乘积(上式)
⑤求表示「可观赏的」贝壳段中「观赏指数」的最大值对 109+7 取模后的结果
注意:求「观赏指数」时的ai是原本的ai,而非替换后的ai
实际上并未真正替换
注意:最大值的取模,而非取模的最大值
假·分析
这题写着好累啊(咋这么多字)
首先,乍一看虽然有点复杂,但好在n不是很大,思路也挺简单:
双重for循环,外层枚举区间长度len,内层枚举区间左端点 l,计算该区间是否能在k次替换内成为对称的
如果能,则计算连乘积,并与最大值做取舍,复杂度O(n2),还是可以承受的。
然后你就会看到一个又大又红的WA (WA地一声哭出来)
真·分析
「醉翁之意不在酒」啊,这出题人是真的黑
那么一大坨题干根本就不是重点,主要问题是这个数据范围(1 ≤ ai ≤ 109+7)(1 ≤ n ≤ 1000)
要命了
这可是连乘积啊,(109+7)1000,打个折109000,这可真是,玉皇大帝来了都没辙
想啥呢,乘起来?桃子
所以作者说要模上109+7,但却是先求最大值再取模,先取模会改变大小关系
也就是说,摆在我们面前的问题是:如何在不求值的情况下,比较两个连乘积的大小
而且最大值无法用特定值表示,改用区间形式(int l, r)
此时,有两种办法:
①取对数
log10(a*b*…*z)==log10(a)+log10(b)+…+log10(z)
这时,109000 便转化为9000,用double比较大小不在话下。
但是,有一个问题,log10()函数计算效率不尽如人意,如果每次直接计算,势必超时(血的教训)
此时我们可以发现,计算存在冗余,可以采用 预处理出对数数组 的方式解决
②两个连乘积中的每一项相除,拆分比较
例:A=a*b*c
B=d*e*f*g*h
t=(a/d)*(b*e)*(c/f)
while(t>=1)t/=g;…t/=h;
if(t>1)A>B;
实质上是比较A/B,做商,拆项可以防止溢出
两种方法都是O(n)地扫描数组,但除法比log更快,所以可以直接计算,无需预处理(这还真不好预处理),开了O2优化后时间大概在700ms左右,卡得死死的。不开就炸了。
当然我觉得第一种更好(200ms),(第二种是比赛时我的暴力美学…先写上再说)
这里采用取对数的方法(赛后补的一发)(更短一些)
"Talk is Cheap. Show me the Code."
#include<stdio.h>
#include<math.h>
typedef long long ll;
const int maxn = 1e3 + 5;
const int mod = 1e9 + 7;
int a[maxn];
double Log[maxn];
struct Node {
int l;
int r;
double sum;
};
int main(void)
{
int n, k;
scanf("%d %d", &n, &k);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]), Log[i] = log10(a[i]);//预处理,否则超时
Node Max = { 1,1,Log[1] };
for (int len = 1; len <= n; len++)
for (int i = 1; i <= n - len + 1; i++) {
int l = i, r = l + len - 1;//计算右端点
int cnt = 0;
while (l < r) {//计算需替换次数
if (a[l] != a[r])
cnt++;
l++, r--;
}
if (cnt <= k) {//若「可观赏的」
l = i, r = l + len - 1;
double sum = 0.0;
for (int j = l; j <= r; j++)
sum += Log[j];
if (sum > Max.sum)
Max = { l,r ,sum };
}
}
ll res = 1;
for (int i = Max.l; i <= Max.r; i++)
res *= a[i], res %= mod;
printf("%lld\n", res % mod);
return 0;
}