【单调队列】【动态规划】[CQBZOJ3059]Bead

题目描述

Alex 喜欢玩网络游戏,认为这是智力和体力的综合锻炼。在一次游戏活动中,
他意外获得了一个传说中威力极其强大的法宝:珠链。
珠链,顾名思义,就是由许多小珠子串起来的一条链。珠子有很多种颜色。
Alex 听说过,只有将珠链打磨纯净,珠链才能发挥最大的威力。
纯净珠链是指这样的珠链:它可以分成若干个长度相等的段,使任何两段的
任何相同位置的珠子的颜色均不同,相同位置指珠子在段内的相对位置相同;而
且每段的长度以及划分的段数也是有规范的,Alex 记得,每段包含的珠子数目
必须在L 到R 之间,而且划分的段数不能少于S。
所谓打磨,就是从珠链的首和尾拿掉连续的若干个珠子。打磨后的纯净珠链
的威力等于它的每个珠子具有的魔力值之和。一个珠子的魔力值只与它在打磨前
的珠链中的位置有关。在查找和分析了大量实验数据以后,Alex 发现珠子的魔
力值等于珠子原来位置编号的约数个数!
兴奋不已的Alex 想将珠链打磨成威力最大的纯净珠链。然而,马上要参加
期末考试的Alex 来不及计算了,你能否帮助Alex 算出最大的威力值呢?

输入
输入文件名为bead.in。
第一行是四个整数N, L, R, S。
第二行是一个长度等于N 的字符串,表示Alex 得到的珠链。字符串的第i
个字符表示珠链的第i 个珠子的颜色。相同字母表示相同颜色。珠子的位置从1
编号到N。

输出
输出文件为bead.out。
输出一行,表示打磨后的纯净珠链的最大威力值。如果无法打磨成满足要求
的纯净珠链,输出-1.

样例输入

7 2 3 2
abcbcaa

样例输出

15

提示

能够打磨出的合乎要求的纯净珠链有三种:bc/aa, abc/bca 和bcb/caa。其中威
力最大的是第三种,其威力值等于2+2+3+2+4+2 = 15。
如果给出的珠链是纯净珠链,那么可以不打磨。纯净珠链必须能划分成不少
于S 个等长的段且每段长度在L 到R 之间。
【数据规模和约定】
30% 的数据:1 ≤ N ≤ 100;
60% 的数据:1 ≤ N ≤ 100,000;
100% 的数据:1 ≤ N ≤ 500,000,1 ≤ L, R, S ≤ N, 0 ≤ R – L ≤ 10.
输入的字符串只包含大写和小写的英文字母。字母区分大小写。

题目分析

首先我们枚举LLenR那么我们可以发现我们有答案那么我们可以预处理Lasti表示队列中第i个元素的i相同的所有位置上的前一个相同的字母出现的位置那么我们有答案就是max{sum[i]sum[i(iLastmaxi)+(iLastmaxi)%Len]}那么我们证明一下这个答案的正确性,首先如果答案不是Lastmaxi那么我们令当前区间的右节点的位置在L那么我们首先可以发现对于[L,i]如果我们要成立那么我们有必须要Last[p]<L那么同理有Last[p]p>Last[p]那么我们有如果LLastmaxi那么得到必有p>L那么我们必定有LLast[p]那么我们发现不满足条件了,那么必定有L>Last[p]因为L尽量小那么我们L=Lastmaxi+1那么同理可证明L只能等于Lastmaxi+1时最优。
注:Lastmaxi表示[1,i]中最大的Last上文中p在后面表示的是Last[p]=Lastmaxi
那么总的算法时间复杂度就是O((RL)N)可以胜任本题目

代码

#include <cstdio> 
#include <cstring> 
#include <algorithm> 
using namespace std; 
const int MAXN = 500000; 
int vis[60][500010], ans=-1, s[MAXN+10], sum[MAXN+10], n, L, R, S; 
void solve(int l){ 
    int Max = 0; 
    for(int i=1;i<=n;i++){ 
        Max = max(Max, vis[s[i]][i%l]); 
        int Lent = i-Max; 
        if(Lent/l >= S){ 
            Lent -= Lent % l; 
            ans = max(ans, sum[i]-sum[i-Lent]); 
        } 
        vis[s[i]][i%l] = i; 
    } 
    memset(vis, 0, sizeof vis); 
} 
char str[MAXN+10]; 
int main(){ 
    scanf("%d%d%d%d%s", &n, &L, &R, &S, str+1); 
    for(int i=1;i<=n;i++){ 
        for(int j=i;j<=n;j+=i) sum[j] ++; 
        sum[i] += sum[i-1]; 
    } 
    for(int i=1;i<=n;i++){ 
        if(str[i] >= 'a' && str[i] <= 'z') s[i] = str[i] - 'a'; 
        else s[i] = str[i] - 'A' + 26; 
    } 
    for(int i=L; i<=R; i++) solve(i); 
    printf("%d\n", ans); 

    return 0; 
} 

posted on 2016-03-08 15:27  JeremyGuo  阅读(196)  评论(0编辑  收藏  举报

导航