題意
給出一個n和一個k,求1~n的每個區間的第k大的數的總和是多少,區間長度小於k的話,貢獻爲0。
題解
因爲給出的n個數是1-n的全排列,所以我們從小開始枚舉第k大的值,即枚舉第k大的值從1到n-k+1。剩下的值不可能有k-1個數比它大所以不可能是第k大數,貢獻爲0,所以不用枚舉。對於每個枚舉的值,從該值的位置向前找k個比它大的數的位置,向後找k個比它大的數的位置。然後就可以計算所有第k大數爲枚舉值的區間個數。枚舉值和區間個數相乘,對於每個枚舉值最後再相加即爲最後答案。
比如我們令k=3,枚舉值爲5,那麼向前和向後比它大的3個數的位置之間位置關係如下:
pre[3]–pre[2]–pre[1]–5=pre[0]=pos[0]–pos[1]–pos[2]–pos[3]
所以我們枚舉區間右邊界i,此時滿足區間第3大數爲5的區間個數爲(pre[i]-pre[i+1])*(pos[k-i]-pos[k-i-1])(你可以把i=0帶入一下就知道了)
可以結合代碼看看
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e5+5;
//a[]保存n個數的值,loc[]保存每個值的位置
int a[maxn],loc[maxn],n,k;
//pre[i]保存前面比位置爲i的數大的數的位置
//pos[i]保存後面比位置爲i的數大的數的位置
//pr[i]保存前面第i個比枚舉值大的數的位置
//po[i]保存後面第i個比枚舉值大的數的位置
//pr_num表示前面比枚舉值大的數的個數
//po_num表示後面比枚舉值大的數的個數
int pre[maxn],pos[maxn],pr[maxn],po[maxn],pr_num,po_num;
void erase(int x) //刪除操作
{
int pp=pre[x];
int nn=pos[x];
if(pre[x]) pos[pp]=nn;
if(pos[x]<=n) pre[nn]=pp;
pre[x]=pos[x]=0;
}
int main()
{
//freopen("in.txt","r",stdin);
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
loc[a[i]]=i;
}
for(int i=1;i<=n;i++) pre[i] = i-1,pos[i] = i+1;
ll ans=0;
for(int i=1;i<=n-k+1;i++)
{
int p = loc[i];
pr_num = po_num = 0;
for(int x=p;x>=1&&pr_num<=k;x=pre[x]) pr[pr_num++] = x;
for(int x=p;x<=n&&po_num<=k;x=pos[x]) po[po_num++] = x;
pr[pr_num++] = 0;
po[po_num++] = n+1;
for(int j=0;j<pr_num-1;j++)
{
if(k-j>0&&k-j<po_num)
{
ans += 1ll*(pr[j]-pr[j+1])*(po[k-j]-po[k-j-1])*i;
}
}
//這裏必須要刪除才能保證對枚舉的每個數pre和pos都是比它大的數的位置
erase(p);
}
printf("%lld\n",ans);
}
//printf("Time used = %.2f\n",(double)clock() / CLOCKS_PER_SEC);
return 0;
}
我可能講的還不是能一下讓人理解,但是你看完題解再多看看幾遍代碼,最好自己畫一下(可以就用樣例),還是能懂的。這裏其實就相當於用數組實現一個鏈表維護從小開始的數的前面和後面比它大的數。