题目链接: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 }
总结:通过从小到大枚举1-n,枚举完一个节点在链表中把他删除,每次枚举的时候这个点都是链表中最小的元素,放心往前往后找k个都是比他大的,感觉这个姿势好厉害。
认识到的不足:可能在确定边界上总是懵逼,以后决定使用画图举例的方式确定边界,然后这个链表的姿势确定是不会,未来可能还得多复习复习这个题。