2017 Multi-University Training Contest

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6058

题目意思:给你一个排列,求所有区间长度大于等于k的区间第k大的数的和……

思路:一开始看到区间k大?结果是所有区间,没那么简单,队友拿一个划分树的模板直接TLE,最后也没有做出来。思路是算出每个点在多少个区间内是第k大的,转换一下问题,找一个区间有k-1个比这个数大的,剩下的数都比他小,这样区间的个数乘以这个数就是这个数的贡献,所以关键在于找那些比这个数大的数都在哪些位置上关键,最好还是从小到大的,前面的思路比赛的时候想到了,就是后面这个优化没想出来,看了题解发现是用链表,感觉真的很6,算是学到了。

具体做法我们建立一个链表这个链表每个点有三个参数pos,pre,nxt,由于我用的是结构体数组建的链表,所以每个点的下标代表他是整个排列中第几小的元素,pos代表他在排列中的位置,pre代表每个点的前续元素的pos,nxt代表他后续元素的pos,刚开始的时候肯定要初始化一下。然后我们先从整个排列中值最小的点开始枚举,这样链表内所有的点都比他大,然后再删除他,这样每次枚举一个点的时候链表中所有其他的点都会比它大,然后每次先往后找k个,再往前找k个。然后卡一个k+1个点的区间。左右区间差乘在一起,最后每个点的贡献加在一起,然后画一个图确定一下边界问题,xjb写一下代码就好了。

代码:

 1 //Author: xiaowuga
 2 #include <iostream>
 3 #include <algorithm>
 4 #include <set>
 5 #include <vector>
 6 #include <queue>
 7 #include <cmath>
 8 #include <cstring>
 9 #include <cstdio>
10 #include <ctime>
11 #include <map>
12 #include <bitset>
13 #include <cctype>
14 #define maxx INT_MAX
15 #define minn INT_MIN
16 #define inf 0x3f3f3f3f
17 #define mem(s,ch) memset(s,ch,sizeof(s))
18 #define nc cout<<"nc"<<endl
19 const long long N=5*100000+10;
20 using namespace std;
21 typedef long long LL;
22 const int MAXBUF = 10000;
23 char buf[MAXBUF], *ps = buf, *pe = buf+1;
24 inline void rnext()
25 {
26     if(++ps == pe)
27         pe = (ps = buf)+fread(buf,sizeof(char),sizeof(buf)/sizeof(char),stdin);
28 }
29 
30 template <class T>
31 inline bool readin(T &ans)
32 {
33     ans = 0;
34     T f = 1;
35     if(ps == pe) return false;//EOF
36     do{
37         rnext();
38         if('-' == *ps) f = -1;
39     }while(!isdigit(*ps) && ps != pe);
40     if(ps == pe) return false;//EOF
41     do
42     {
43         ans = (ans<<1)+(ans<<3)+*ps-48;
44         rnext();
45     }while(isdigit(*ps) && ps != pe);
46     ans *= f;
47     return true;
48 }
49 struct node{
50     int pos,pre,nxt;
51 }p[N];
52 int n,k;
53 LL ans=0;
54 void read(){
55    for(int i=1;i<=n;i++){
56        int x; readin(x);
57        p[x].pos=i;
58        p[i].pre=i-1;
59        p[i].nxt=i+1;
60    } 
61    //把两个边界插进去
62    p[0].pre=0;
63    p[n+1].nxt=n+1;
64 }
65 void solve(){
66     int lc,rc,rq[85];
67     LL tans;
68     for(int i=1;i<=n;i++){
69        lc=rc=tans=0;
70        int t=p[i].pos;
71        for(int j=t;j<=n&&rc<k;j=p[j].nxt){//该节点往前找k个比整个排列第i大的数大的数
72             rq[++rc]=p[j].nxt-j;
73        }
74        for(int j=t;j>0&&lc<k;j=p[j].pre){//该节点往后找k个比整个排列第i大的数大的数
75             lc++;
76             if(k-lc+1>rc) continue;//左右找到的数量大于k的时候,开始计算区间
77             tans+=(j-p[j].pre)*rq[k-lc+1];//此时找到的区间和第一个找到的右区间匹配 
78        }
79        ans+=tans*i;//计算该点贡献
80        //删除节点
81        p[p[t].pre].nxt=p[t].nxt;
82        p[p[t].nxt].pre=p[t].pre;
83     }
84     cout<<ans<<endl;
85 }
86 int main() {
87     int T;
88     readin(T);
89     while(T--){
90         readin(n);readin(k);
91         k=min(k,80);
92         read();
93         ans=0;
94         solve(); 
95     }
96     return 0;
97 }
View Code

总结:通过从小到大枚举1-n,枚举完一个节点在链表中把他删除,每次枚举的时候这个点都是链表中最小的元素,放心往前往后找k个都是比他大的,感觉这个姿势好厉害。

认识到的不足:可能在确定边界上总是懵逼,以后决定使用画图举例的方式确定边界,然后这个链表的姿势确定是不会,未来可能还得多复习复习这个题。

原文地址:https://www.cnblogs.com/xiaowuga/p/7272742.html