题目:http://acm.hdu.edu.cn/showproblem.php?pid=6058
分析题目的时候,由于枚举的区间很多,而第k大的值范围小,应该要想到去枚举第k大的值然后找到这个值对答案的贡献。
题解:我们只要求出对于一个数x左边最近的k个比他大的和右边最近k个比他大的,扫一下就可以知道有几个区间的kk大值是xx.(这个地方自己举几个例子就知道了)
我们考虑从小到大枚举x,每次维护一个链表(我写了一个双链表),链表里只有大于x的数,每次求x对答案的贡献的时候,直接在链表中x的位置左右k个值扫一边就可以了。
ac代码:
#include <cstdio> #include <iostream> #include <cstring> using namespace std; typedef long long ll; struct node { ll key; ll l,r; }num[500001]; ll a[500001]; ll pos[500001]; ll pre[81],nex[81]; int main() { int t; scanf("%d",&t); while(t--) { int n,k; scanf("%d %d",&n,&k); num[1].l=-1; num[n].r=-1; for(int i=1;i<=n;i++) { scanf("%d",&a[i]); if(i!=1) num[i].l=i-1; if(i!=n) num[i].r=i+1; num[i].key=a[i]; pos[a[i]]=i; } ll sum=0; for(int i=1;i<=n;i++) { fill(pre+1,pre+k+1,-1); fill(nex+1,nex+k+1,-1); ll po=pos[i]; ll temp=num[po].l; int ret=1; pre[0]=nex[0]=po; while(temp!=-1 && ret<=k) { pre[ret++]=temp; temp=num[temp].l; } temp=num[po].r; ret=1; while(temp!=-1 && ret<=k) { nex[ret++]=temp; temp=num[temp].r; } /* for(int i=1;i<=k;i++) cout<<pre[i]<<' '; cout<<endl; for(int i=1;i<=k;i++) cout<<nex[i]<<' '; cout<<endl; */ for(int j=0;j<k;j++) { if(pre[k-j-1]==-1 || nex[j]==-1) continue; ll ans=pre[k-j-1]-pre[k-j]; ll ans2=nex[j+1]-nex[j]; if(pre[k-j]==-1) ans=pre[k-j-1]; if(nex[j+1]==-1) ans2=n-nex[j]+1; // cout<<ans<<' '<<ans2<<endl; sum+=ans*ans2*(ll)i; } // delete ll nl=num[po].l; ll nr=num[po].r; num[nl].r=nr; num[nr].l=nl; } printf("%lld\n",sum); } return 0; }
用链表去维护左右比x大的数还是很神奇的(从小到大去计算贡献值,每次算完小的值就从链表里面删去,,,, 厉害了)。。这个真心没想到