洛谷 P2048 [NOI2010]超级钢琴 || Fantasy

https://www.luogu.org/problemnew/show/P2048

http://www.lydsy.com/JudgeOnline/problem.php?id=2006

首先计算出数列的前缀和数组a[0]..a[n],那么问题转化为:从[0,n]中选取任意两个数(i,j)使得i<j,计算a[j]-a[i];对于所有合法的数对(i,j),求第k大的a[j]-a[i]的值。

考虑枚举i,对于每一个i要得到第k大的a[j]-a[i],则需要从[i+1,n]中选取j使得a[j]是a[i+1]到a[n]中第k大的。

用一个优先队列维护一下,然后问题就只剩区间k大了。可以用静态主席树搞一下。

但是你会发现T到飞起...常数太大了2333333

本来动态开点偷懒都不行了

 1 #pragma GCC optimize("Ofast")
 2 #pragma GCC optimize("inline","fast-math","unroll-loops","no-stack-protector")
 3 #pragma GCC diagnostic error "-fwhole-program"
 4 #pragma GCC diagnostic error "-fcse-skip-blocks"
 5 #pragma GCC diagnostic error "-funsafe-loop-optimizations"
 6 #pragma GCC diagnostic error "-std=c++14"
 7 #include<cstdio>
 8 #include<algorithm>
 9 #include<queue>
10 #include<tr1/unordered_map>
11 #define mid ((l+r)>>1)
12 using namespace std;
13 typedef long long LL;
14 typedef pair<LL,int> P;
15 tr1::unordered_map<LL,int> ma;
16 LL tmp[500100],ma2[500100];
17 int mem,lc[40000100],rc[40000100],dat[40000100],root[500100];
18 LL ll,rr,totn;
19 LL L;int x;
20 //root[i]表示[i+L,i+R]区间的线段树,也就是选i+1号为左端点时右端点可取范围
21 void addx(LL l,LL r,int& num)
22 {
23     int t=num;num=++mem;//if(mem>=40000000){puts("test");exit(0);}
24     lc[num]=lc[t];rc[num]=rc[t];dat[num]=dat[t];
25     if(l==r)    {dat[num]+=x;return;}
26     if(L<=mid)    addx(l,mid,lc[num]);
27     else    addx(mid+1,r,rc[num]);
28     dat[num]=dat[lc[num]]+dat[rc[num]];
29 }
30 LL getx(LL l,LL r,int num)//第x大
31 {
32     if(l==r)    return l;
33     if(dat[rc[num]]>=x)    return getx(mid+1,r,rc[num]);
34     else    return x-=dat[rc[num]],getx(l,mid,lc[num]);
35 }
36 int n,k,lx,rx;LL ans;
37 LL a[500010];int nownum[500010];
38 priority_queue<P> q;
39 //P(当前和,i)(当前和等于[i+L,i+R]的第nownum[i]大-a[i])
40 //void out(LL l,LL r,LL num)
41 //{
42 //    if(l==r)    {for(LL i=1;i<=dat[num];i++)    printf("%lld ",l);return;}
43 //    out(l,mid,lc[num]);out(mid+1,r,rc[num]);
44 //}
45 //void out()
46 //{
47 //    queue<P> qq;P t;
48 //    while(!q.empty())    {t=q.top();q.pop();printf("%lld %lld
",t.first,t.second);qq.push(t);}
49 //    while(!qq.empty())    {t=qq.front();qq.pop();q.push(t);}
50 //}
51 int main()
52 {
53     int l=0,r=-1,i;P t;
54     scanf("%d%d%d%d",&n,&k,&lx,&rx);
55     for(i=1;i<=n;i++)    scanf("%lld",&a[i]),a[i]+=a[i-1],tmp[++tmp[0]]=a[i];
56     sort(tmp+1,tmp+tmp[0]+1);totn=unique(tmp+1,tmp+tmp[0]+1)-tmp-1;
57     for(i=1;i<=totn;i++)    ma[tmp[i]]=i,ma2[i]=tmp[i];
58     for(i=1;i<=n;i++)    a[i]=ma[a[i]];
59     ll=1;rr=totn;
60     while(r<n&&r<rx)    r++,L=a[r],x=1,addx(ll,rr,root[0]);
61     while(l<=n&&l<lx)    L=a[l],x=-1,addx(ll,rr,root[0]),l++;
62     if(l<=r)
63     {
64         x=nownum[0]=1;
65         q.push(P(ma2[getx(ll,rr,root[0])]-ma2[a[0]],0));
66     }
67     for(i=1;i<n;i++)
68     {
69         root[i]=root[i-1];
70         while(r<n&&r<i+rx)    r++,L=a[r],x=1,addx(ll,rr,root[i]);
71         while(l<=n&&l<i+lx)    L=a[l],x=-1,addx(ll,rr,root[i]),l++;
72         if(l<=r)
73         {
74             x=nownum[i]=1;
75             q.push(P(ma2[getx(ll,rr,root[i])]-ma2[a[i]],i));
76         }
77     }
78     //out();
79     //printf("
%lld",mem);
80     //return 0;
81 
82     for(i=1;i<=k;i++)
83     {
84         t=q.top();q.pop();
85         //printf("a%lld
",t.first);
86         ans+=t.first;
87         ++nownum[t.second];
88         //l=min(n+1,t.second+lx);r=min(n,t.second+rx);
89         //if(r-l+1<nownum[t.second])    continue;
90         if(dat[root[t.second]]<nownum[t.second])    continue;
91         x=nownum[t.second];
92         q.push(P(ma2[getx(ll,rr,root[t.second])]-ma2[a[t.second]],t.second));
93     }
94     printf("%lld",ans);
95 
96     //for(i=0;i<n;i++)    {out(ll,rr,root[i]);puts("");}
97     return 0;
98 }

错误记录:90行写成dat[t.second];动态开点(没有离散化)时误以为值域只有-1000到1000(但事实上值域是前缀和的值域,-5e8到5e8)

开了奇怪的Ofast后A掉了....

其实有更优越的做法,就是维护优先队列时,每次搞完[l,r]区间最大值(在k位置)后,就把它拆成[l,k-1]和[r+1,k](还有这两个区间内分别的最小值)分别放回去,这样恰好去掉了之前的最大值,又不破坏次大值、第三大值、....。这样只需要ST表预处理一下然后RMQ就行了,好写&常数小


这里有一个一模一样的题,并过来

原文地址:https://www.cnblogs.com/hehe54321/p/8586056.html