1008 K-th Closest Distance(在线查询,二分+主席树
题意:给定数组a ,每次查询给定l,r,p,k ,求l~r区间内第k小的距离|p-ai|,强制在线查询:每次给定的四个参数必须异或上一次的查询结果ans;
分析:
1.求期间第k大用主席树
2.绝对值不是一个很好处理的东西,直接查询是不行的,主席树不但可以直接求区间第k小(递归查询左子树的权值),也可以查询区间内某一范围的值的数量(求范围内树的权值),进行问题转化(想到昨天看企鹅公路上男主爸爸的问题解决方法:1.大问题分解成小问题 2.转换角度 3.寻找相同或相似的问题):
求第k小的距离|p-ai| --> 处于0到|p-ai|的值恰好有k个时|p-ai|的值(实际上这就是主席树解决区间第k小问题的方法
|p-ai| --> p-ai~p+ai ,去掉绝对值符号问题会容易一些,而且p-ai~p+ai是可以直接用主席树查询的
所以原问题就变为:求区间处于 p-ai~p+ai 的值 恰好有 k 个时 |p-ai| 的值 ,k的值随着ai的变大是单调递增的 ,
所以想到二分ai求解,二分检查用主席树的查询完成
3.补题过程中的问题:
1)主席树并没透彻理解查询第k小/大问题的解决原理,实际上并没有真正了解主席树结点维护的值的含义,所以不知道主席树可以查询区间内某一范围的值的数量
2)写出来一直tle,觉得是板子的问题(这个板子需要离散化),但调试后发现真正的问题是在线查询后lstans的值没有初始化orz,改了后不到5000ms就过了(题限15000ms),板子还是很可以滴
题解:
1008. K-th Closest Distance
Using segment tree, we can find the number of values smaller than p in [L, R] within O(log(n)).
So by using binary search method, we can find the answer in O(log(n)^2) time.
Total time complexity is O(N log(N) + Q log(N)^2).
代码:
#include<bits/stdc++.h> #define fi first #define se second #define rep( i ,x ,y ) for( int i= x; i<= y ;i++ ) #define reb( i ,y ,x ) for( int i= y; i>= x ;i-- ) #define mem( a ,x ) memset( a ,x ,sizeof(a)) #define lson ls[o] ,l ,m #define rson rs[o] ,m+1 ,r using namespace std; typedef long long ll; typedef long double ld; typedef pair<int ,int> pii; typedef pair<ll ,ll> pll; typedef pair<string ,int> psi; const int inf = 0x3f3f3f3f; const int N = 100005; const int M = 1000005; int n ,q ,T ,tot ,res ,tst; int mn ,mx; int l ,r ,p ,k ,lstans=0; int b[N] ,a[N] ,ls[N*64] ,rs[N*64] ,sum[N*64] ,rt[N*64]; int mp[M]; void build( int &o ,int l ,int r ){ o = ++tot; sum[o] = 0; if( l==r )return; int m =(l+r)>>1; build( lson ); build( rson ); //cout<<l<<" "<<r<<endl; } void updata( int &o ,int l ,int r ,int last ,int p ){ o = ++tot; ls[o] = ls[last]; rs[o] = rs[last]; sum[o] = sum[last]+1; if( l==r )return; int m = ( l+r )>>1; if( p <= m)updata( lson ,ls[last] ,p); if( p > m )updata( rson ,rs[last] ,p); } // query 查询第k大 int query( int ss ,int tt ,int l ,int r ,int k ){ if( l==r )return l; int m = (l+r) >>1; int cnt = sum[ ls[tt] ] - sum[ ls[ss] ]; if( k <= cnt )return query( ls[ss] ,ls[tt] ,l ,m ,k ); else return query( rs[ss] ,rs[tt] ,m+1 ,r ,k - cnt ); } // ask查询某一范围的数值 // ask 和 query的原理都是利用主席树所存的内容 ,只不过利用的方式不同 int ask( int lst ,int now ,int l ,int r ,int L ,int R ){ // lst 先前插入数值时间标号(查询参数L),now 现在插入数值时间标号(查询参数R) //cout<<"l "<<" r "<<l <<" "<<r<<endl; int mid = ( l+r )>>1; int ans = 0 ; if( L <= l && r <= R) return sum[ now ] - sum[ lst ]; if( L<=mid )ans += ask( ls[lst] ,ls[now] ,l ,mid ,L ,R ); if( R >mid )ans += ask( rs[lst] ,rs[now] ,mid+1 ,r ,L ,R); return ans; } int main( ){ scanf("%d" ,&T); tst = tot; while( T-- ){ lstans = 0; //第一次写在线查询没注意lstans的初始化orz tot = 0; //每次初始化tot就行 mn = inf; mx = 0; //mem(rt ,0); //mem(ls ,0); //mem(rs ,0); //不用mem就行,因为build时就完成了初始化 scanf("%d%d" ,&n ,&q ); rep( i ,1 ,n )scanf("%d" ,&a[i] ), b[i] = a[i] ,mn=min(mn ,a[i]) ,mx =max(mx ,a[i]); sort( b+1 ,b+1+n ); int sz = unique( b+1 ,b+n+1 ) - (b+1); build( rt[0] ,1 ,sz ); rep( i ,1 ,n )a[i] = lower_bound( b+1 ,b+1+sz ,a[i]) - b; rep( i ,1 ,n )updata( rt[i] ,1 ,sz ,rt[i-1] ,a[i]); rep( i ,1 ,q ){ scanf("%d%d%d%d" ,&l ,&r ,&p ,&k ); l ^= lstans; r ^= lstans; p ^= lstans; k ^= lstans; //强制在线 int x=0 ,y = 1e6 ,mid ,ans; int pl ,pr; while( x<=y ){ //cout<<x <<" "<<y<<endl; mid = ( x+y )>>1; pr = lower_bound( b+1 ,b+1+n ,min(mx ,p+mid) )- b ; pl = lower_bound( b+1 ,b+1+n ,max(mn ,p-mid) )- b ; //cout<<mid<<endl; //cout<<pl<<" "<<pr<<endl; if( b[pr] > min(mx ,p+mid) )pr--; if( pr < pl ){ x = mid+1; continue; } res = ask( rt[l-1] ,rt[r] ,1 ,sz ,pl ,pr ); //cout<<res<<endl; if( res >= k )ans = mid ,y = mid-1; else x = mid+1; } lstans = ans; printf("%d " ,ans); } //cout<< ask( rt[0] ,rt[5] ,1 ,sz ,1 ,5 ) <<endl ; } return 0; }
题意:给出n个石子,重量分别为1→n1→n,现在要求分成kk堆,要求每一堆的重量相同,问是否可以构造
分析:令 t = n/k ,表示划分后每一堆的石子数;
1.当t为偶数,此时n一定为偶数,利用等差数列倒序相加的性质,很容易构造出n/2对质量相等的堆,平均分配即可
2.当t为奇数,问题分解:先划分前3k个石子为k堆,质量相等,每堆三个 ,则t-=3剩下的石子可以变为已知问题1求解
3k的划分方法: 发现直接倒序相加不行,尝试将其分为可以倒序相加的两部分
构造 : 最后k个等差递减1 ,前2k个合并成一个等差递增1
前2k个合并方法:前k个等差递增2 ,后k个等差递减1,合并后则等差递减1
注意构造的拼接处,由k的奇偶性不同而变化
eg:
2k~3k | k~2k | 1~k |
27 | 13 | 2 |
26 | 12 | 4 |
25 | 11 | 6 |
24 | 10 | 8 |
23 | 18 | 1 |
22 | 17 | 3 |
21 | 16 | 5 |
20 | 15 | 7 |
19 | 14 | 9 |
也可以构造长度为奇数的幻方,剩下的也是偶数
3.注意n=1,k=1的特判
题解:
1003. Divide the stones
If the total number of stones is a multiple of k, we can always divide the stones. Otherwise, we can’t.
If is even, you can find solution quite easily.
If is an odd number, we divide first 3k stones into k groups with exactly 3 stones and equal weight.
After that, you can divide remaining stones by the way you did when is even.
Time complexity is O(n).
代码:
#include<bits/stdc++.h> #define fi first #define se second #define rep( i ,x ,y ) for( int i= x; i<= y ;i++ ) #define reb( i ,y ,x ) for( int i= y; i>= x ;i-- ) #define mem( a ,x ) memset( a ,x ,sizeof(a)) #define lson ls[o] ,l ,m #define rson rs[o] ,m+1 ,r using namespace std; typedef long long ll; typedef long double ld; typedef pair<int ,int> pii; typedef pair<ll ,ll> pll; typedef pair<string ,int> psi; const int inf = 0x3f3f3f3f; const int N = 100005; const int M = 1000005; ll T ,n ,k ,t; ll s ,head ,tail; vector <ll> ans[100005]; // 偶数构造 void even( ){ head = 1 ,tail = n; //构造:对称构造 rep( i ,1 ,k ){ rep( j ,1 ,t/2 ){ ans[i].push_back(head++); ans[i].push_back(tail--); } } return ; } void odd( ){ // 前3*k构成k堆 ,每堆3个 // 构造 : 最后k个等差递减1 ,前2k个合并成一个等差递增1 // 前2k个合并方法:前k个等差递增2 ,后k个等差递减1 // 注意构造的拼接处,由k的奇偶性不同而变化 int tmp; rep(i ,0 ,k-1)ans[i+1].push_back( 3*k-i ); //前3k if( k&1 ){ tmp = k/2; rep( i ,0 ,tmp-1){ ans[i+1].push_back(2*(i+1)); ans[i+1].push_back(k+tmp-i); } rep( i ,tmp ,k-1 ){ ans[i+1].push_back(2*(i-tmp)+1); ans[i+1].push_back(2*k+tmp-i); } } else{ tmp = k/2; rep( i ,0 ,tmp-1){ ans[i+1].push_back(2*i+1); ans[i+1].push_back(k+tmp-i); } rep( i ,tmp ,k-1 ){ ans[i+1].push_back(2*(i-tmp)); ans[i+1].push_back(2*k+tmp-i); } } head = 3*k+1 ,tail = n; //构造:对称构造 t -= 3; rep( i ,1 ,k ){ rep( j ,1 ,t/2 ){ ans[i].push_back(head++); ans[i].push_back(tail--); } } return; } int main( ){ scanf("%lld" ,&T); while( T-- ){ scanf("%lld%lld" ,&n ,&k); t = n/k; s = n*(n+1)/2; if( s%k || n%k ){ printf("no "); continue; } if( n==1 && k==1 ){ printf("yes 1 "); continue; } if( ~t&1 ) even( ); else { if( t<3 ){ printf("no "); continue; } else odd(); } printf("yes "); rep( i ,1 ,k ){ int sz = ans[i].size( ); rep( j ,0 ,sz-1 ){ if( j )printf(" "); printf("%lld" ,ans[i][j]); } printf(" "); ans[i].clear( ); } } return 0; }
剩下的先不补