灵知的太阳信仰

题目大意

每个位置有两个值ai和bi。
给一个序列分成若干连续段,使得每个段内a值互不相同,代价是b的最大值。求最小代价。

DP

我们思考设fi表示给1~i分段的最小代价。
我们可以处理出ci表示一个最小的k使得[k,i]没有重复a。
这个随便用个桶就实现了吧。。
那么 Fi=mini1j=ci1(Fj+max(j+1,i))
这个dp是n^2的。

随机数据下该dp的复杂度

我们思考一个40分的暴力,这与我们每次从一个位置i往左,走多少步会出现区间内有相同数有关。
然后呢,对于每个i每次我们期望会在多少步后就结束循环?以下p代表权值范围。
假设i步就结算,那么花费了i步,概率是i-1步内未结束,在第i步时结束了。P(i-1步内未结束)=P(前i-1个互不相同)=(p/p)[(p-1)/p][(p-2)/p]……[(p-i)/p]=p!/[(p-i-1)!*p^(i-1)]
P(在第i步恰好结束)=P(i-1步内未结束)P(第i个与前i-1个中某一个相同)=p!/[(p-i-1)!*p^(i-1)](i-1)/p
对期望的贡献为P(在第i步恰好结束)*i
我们计算一下10^5时的期望,大约为400+。
因此该暴力算法的期望复杂度确实能通过全部的数据。
最后谴责,出题人出题如果要随机数据,应自己实践暴力来确保暴力不会过。不然在期望的情况下,一切均有可能。

正解

显然构造数据能卡掉40分暴力。
思考正解?可以想到根据后面那个max值分成若干段,那么我们维护了一个单调递减的形状,像单调栈一样,每一块可以维护其代表我们的j可选择的区间。
首先思考两个单调性。
ci<=ci+1
fi<=fi+1
单调性证明:显然。
我们维护单调队列,按照max值维护。每次将新的加入队尾不断踢出队尾,同时检验队首是否包含ci并不断踢出队首。
考虑一个块的贡献。对于不是队首的块,其不包含ci,因此可以随便选,由fi<=fi+1我们应该选择最左端的,因此f[最左端]+对应max值是该块贡献。
那么队首呢?队首肯定包含ci,选最左也不能选过ci,因此贡献为f[ci-1]+对应max值是队首贡献。
每次找到贡献最小的块就是fi,用数据结构维护。我懒就打了set。

#include<cstdio>
#include<algorithm>
#include<set>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=100000+10,inf=2000000050;
multiset<int> s;
int f[maxn],a[maxn],b[maxn],c[maxn],cnt[maxn],dl[maxn],L[maxn],g[maxn];
int i,j,k,l,t,n,m,head,tail;
int read(){
    int x=0,f=1;
    char ch=getchar();
    while (ch<'0'||ch>'9'){
        if (ch=='-') f=-1;
        ch=getchar();
    }
    while (ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
int main(){
    freopen("array.in","r",stdin);freopen("array.out","w",stdout);
    n=read();
    fo(i,1,n) a[i]=read(),b[i]=read();
    j=1;
    fo(i,1,n){
        cnt[a[i]]++;
        while (cnt[a[i]]>1){
            cnt[a[j]]--;
            j++;
        }
        c[i]=j;
    }
    fo(i,1,n) f[i]=inf;
    head=1;
    fo(i,1,n){
        t=i-1;
        while (head<=tail&&b[i]>=dl[tail]){
            s.erase(s.find(g[tail]));
            t=L[tail];
            tail--;
        }
        dl[++tail]=b[i];
        L[tail]=t;
        while (head<tail&&L[head+1]-1<c[i]-1){
            s.erase(s.find(g[head]));
            head++;
        }
        if (head==tail){
            g[tail]=f[c[i]-1]+dl[tail];
            s.insert(g[tail]);
        }
        else{
            g[tail]=f[L[tail]]+dl[tail];
            s.insert(g[tail]);
            s.erase(s.find(g[head]));
            g[head]=f[c[i]-1]+dl[head];
            s.insert(g[head]);
        }
        f[i]=*s.begin();
    }
    printf("%d\n",f[n]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值