题目
Count the Colors ZOJ - 1610
题意:给定 n 个操作,每次操作将区间 [ l , r ] 染成颜色 c 。颜色会覆盖,问最终每种颜色出现的段数。 ( n ≤ 8000 , 1 ≤ l , r ≤ 8000 ) (n\le 8000 ,1\le l,r\le 8000) (n≤8000,1≤l,r≤8000)
思路:线段树区间染色。
- 先区间更新,没有颜色或者由多种颜色设为 -1 。
- 然后在区间查询 [1,8000] ,最直观的就是查询到 L==R 的时候看颜色是否和上一个相同。也可以在查询到 s t [ r t ] ! = − 1 st[rt]!=-1 st[rt]!=−1 的时候,做判断。
#include <bits/stdc++.h>
#define ll long long
#define ls (rt<<1)
#define rs (rt<<1|1)
using namespace std;
const int maxn=8000+10;
int n;
int st[maxn<<2];
void pushDown(int rt)
{
if(st[rt]!=-1)
{
st[ls]=st[rt];
st[rs]=st[rt];
st[rt]=-1;
}
}
void build(int rt,int L,int R)
{
st[rt]=-1;
if(L==R) return;
int mid=(L+R)>>1;
build(ls,L,mid);
build(rs,mid+1,R);
}
void update(int rt,int l,int r,int L,int R,int c)
{
if(l<=L&&R<=r)
{
st[rt]=c;
return;
}
pushDown(rt);
int mid=(L+R)>>1;
if(l<=mid) update(ls,l,r,L,mid,c);
if(r>mid) update(rs,l,r,mid+1,R,c);
}
int last;
map<int,int> mp;
void query(int rt,int L,int R)
{
if(L==R)
{
if(st[rt]==-1) last=st[rt];
else if(st[rt]!=last)
{
mp[st[rt]]++;
last=st[rt];
}
return;
}
pushDown(rt);
int mid=(L+R)>>1;
query(ls,L,mid);
query(rs,mid+1,R);
}
void query2(int rt,int L,int R)
{
if(st[rt]!=-1)
{
if(st[rt]!=last)
mp[st[rt]]++,last=st[rt];
return;
}
if(L==R)
{
last=-1;
return;
}
pushDown(rt);
int mid=(L+R)>>1;
query(ls,L,mid);
query(rs,mid+1,R);
}
int main()
{
while(~scanf("%d",&n))
{
mp.clear();
build(1,1,8000);
for(int i=1; i<=n; ++i)
{
int l,r,dx;
scanf("%d%d%d",&l,&r,&dx);
if(l+1>r) continue;
update(1,l+1,r,1,8000,dx);
}
last=-1;
query2(1,1,8000);
for(auto x : mp)
{
int col=x.first,seg=x.second;
printf("%d %d\n",col,seg);
}
puts("");
}
return 0;
}
Mayor’s posters POJ - 2528
链接:https://vjudge.net/problem/POJ-2528
题意:给定 n 个操作,每次操作将区间 [ l , r ] 染成颜色 i (第 i 个操作染成颜色 i ) 。颜色会覆盖,问最终能够看到的颜色有几种。 ( 1 ≤ n ≤ 10000 , 1 ≤ l , r ≤ 1 0 7 ) (1\le n\le 10000 ,1\le l,r\le 10^7) (1≤n≤10000,1≤l,r≤107)
思路:线段树区间染色。这道题和上题类似,不同在于问法,query 的时候是统计染色的数量
- 先对 [ l , r ] 做离散化
- 区间更新时,需要找到 [ l , r ] 维护的段的 [ l’ , r’ ] 。没有颜色或者由多种颜色设为 -1 。
- 然后在区间查询 [ 1 , tot ] (tot 是离散化后数组的大小)。当 st [1]!=-1 或者 L == R 时,统计答案即可。
B. Light bulbs
链接:https://www.jisuanke.com/contest/3003/challenges
题意:给定 n 盏灯,m 个操作,每次对区间 [ l , r ] 内的灯的状态进行翻转。问最终亮灯的数量。 ( 1 ≤ n ≤ 1 0 6 , 1 ≤ m ≤ 1000 ) (1\le n \le 10^6 , 1\le m \le 1000) (1≤n≤106,1≤m≤1000),内存只有 8 M
思路:按理说 n 只有 1 0 6 10^6 106,nlogn的时间复杂度是可以接受的,但是内存只有 8 M。因此需要离散化
实现
- 离散化之后,线段树每个叶节点维护一段内亮灯的数量。根节点维护多段的和
- 每次更新就是:用区间长度 减去 原来亮灯的数量。同时更新lazy
- 最后 st [1] 就是答案。
#include <bits/stdc++.h>
#define ls (rt<<1)
#define rs (rt<<1|1)
using namespace std;
const int maxn=1000+5;
int t,n,m;
pair<int,int> p[maxn];
int st[maxn<<3],lazy[maxn<<3];
vector<int> allx;
void pushDown(int rt,int L,int R)
{
if(lazy[rt])
{
int mid=(L+R)>>1;
st[ls]=allx[mid]-allx[L-1]-st[ls];
st[rs]=allx[R]-allx[mid]-st[rs];
lazy[ls]^=1;
lazy[rs]^=1;
lazy[rt]=0;
}
}
void pushUp(int rt)
{
st[rt]=st[ls]+st[rs];
}
void build(int rt,int L,int R)
{
lazy[rt]=st[rt]=0;
if(L==R) return;
int mid=(L+R)>>1;
build(ls,L,mid);
build(rs,mid+1,R);
}
void update(int rt,int l,int r,int L,int R)
{
if(l<=L&&R<=r)
{
st[rt]=allx[R]-allx[L-1]-st[rt];
lazy[rt]^=1;
return;
}
pushDown(rt,L,R);
int mid=(L+R)>>1;
if(l<=mid) update(ls,l,r,L,mid);
if(r>mid) update(rs,l,r,mid+1,R);
pushUp(rt);
}
int main()
{
int Case=0;
scanf("%d",&t);
while(t--)
{
allx.clear();
scanf("%d%d",&n,&m);
for(int i=1; i<=m; ++i)
{
int l,r;
scanf("%d%d",&l,&r);
r++;
p[i]= {l,r};
allx.push_back(l);
allx.push_back(r);
}
sort(allx.begin(),allx.end());
allx.resize(unique(allx.begin(),allx.end())-allx.begin());
int tot=allx.size()-1;
build(1,1,tot);
for(int i=1; i<=m; ++i)
{
int l=lower_bound(allx.begin(),allx.end(),p[i].first)-allx.begin()+1;
int r=lower_bound(allx.begin(),allx.end(),p[i].second)-allx.begin();
update(1,l,r,1,tot);
}
printf("Case #%d: %d\n",++Case,st[1]);
}
return 0;
}
F. Greedy Sequence (滑动窗口 || 线段树 || 主席树)
链接:https://nanti.jisuanke.com/t/41303
题意:给定一个 n 的排列和 k。对于每个数 i,它的位置为 p o s [ i ] pos[i] pos[i] ,查找 [ p o s [ i ] − k , p o s [ i ] + k ] [pos[i]-k,pos[i]+k] [pos[i]−k,pos[i]+k] 中小于第一个小于 i 的数 x ,答案就是 a n s [ i ] = a n s [ x ] + 1 ans[i]=ans[x]+1 ans[i]=ans[x]+1。
思路:对于每个数 i ,在给定区间内,查找第一个小于 i 的数。
实现:
- 滑动窗口:这里比较特殊, a i a_i ai 是序列上的一个数,可以用set 维护一个长度为 k 的区间,然后搜出左边 k 个中的小于 a i a_i ai 最大值,右边 k 个中的小于 a i a_i ai 最大值,最后两边取最大值即可
- 线段树:先将小于自己的数先更新到线段树上,然后就查询这个区间上的最大值,就找到了。边更新边寻找答案。对自己答案有贡献的做更新,有影响的先不更新。
- 主席树:在主席树上区间 [ l , r ] [l,r] [l,r] 表现为时间序,小于 i i i 表现为 i 的位置 p 左边第一个有数的位置,即从位置 p -1 找到 1 。
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int t,n,k;
int l[maxn],r[maxn];
int a[maxn],pos[maxn],ans[maxn];
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&k);
for(int i=1; i<=n; ++i)
{
scanf("%d",&a[i]),pos[a[i]]=i;
l[i]=r[i]=ans[i]=0;
}
set<int> s;
for(int i=1; i<=n; ++i)
{
if(i-k-1>=1) s.erase(a[i-k-1]);
s.insert(a[i]);
auto it=s.lower_bound(a[i]);
if(it!=s.begin()) l[i]=*(--it);
}
s.clear();
for(int i=n; i>=1; --i)
{
if(i+k+1<=n) s.erase(a[i+k+1]);
s.insert(a[i]);
auto it=s.lower_bound(a[i]);
if(it!=s.begin()) r[i]=*(--it);
}
for(int i=1; i<=n; ++i)
{
int p=pos[i];
int x=max(l[p],r[p]);
ans[i]=ans[x]+1;
}
for(int i=1; i<=n; ++i)
printf("%d%c",ans[i],i==n?'\n':' ');
}
return 0;
}
#include <bits/stdc++.h>
#define ls (rt<<1)
#define rs (rt<<1|1)
using namespace std;
const int maxn=1e5+5;
int t,n,k;
int a[maxn],pos[maxn],ans[maxn];
int st[maxn<<2];
void update(int rt,int p,int L,int R,int v)
{
if(L==R)
{
st[rt]=v;
return;
}
int mid=(L+R)>>1;
if(p<=mid) update(ls,p,L,mid,v);
if(p>mid) update(rs,p,mid+1,R,v);
st[rt]=max(st[ls],st[rs]);
}
int query(int rt,int l,int r,int L,int R)
{
if(l<=L&&R<=r) return st[rt];
int mid=(L+R)>>1;
int ans=0;
if(l<=mid) ans=max(ans,query(ls,l,r,L,mid));
if(r>mid) ans=max(ans,query(rs,l,r,mid+1,R));
return ans;
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&k);
for(int i=1; i<=n; ++i)
scanf("%d",&a[i]),pos[a[i]]=i;
memset(st,0,sizeof(st));
for(int i=1; i<=n; ++i)
{
int p=pos[i];
int l=max(1,pos[i]-k);
int r=min(n,pos[i]+k);
int x=query(1,l,r,1,n);
ans[i]=ans[x]+1;
update(1,p,1,n,i);
}
for(int i=1; i<=n; ++i)
printf("%d%c",ans[i],i==n?'\n':' ');
}
return 0;
}
主席树
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int t,n,k;
int a[maxn],pos[maxn],ans[maxn];
int root[maxn],st[maxn*40],ls[maxn*40],rs[maxn*40],no;
int build(int L,int R)
{
int rt=++no;
if(L==R) return rt;
int mid=(L+R)>>1;
ls[rt]=build(L,mid);
rs[rt]=build(mid+1,R);
return rt;
}
int update(int pre,int p,int L,int R)
{
int rt=++no;
ls[rt]=ls[pre];
rs[rt]=rs[pre];
st[rt]=st[pre]+1;
if(L==R) return rt;
int mid=(L+R)>>1;
if(p<=mid) ls[rt]=update(ls[pre],p,L,mid);
if(p>mid) rs[rt]=update(rs[pre],p,mid+1,R);
return rt;
}
int query(int pre,int now,int p,int L,int R)
{
if(st[now]-st[pre]==0) return 0;
if(L==R) return L<=p-1?L:0;
int mid=(L+R)>>1;
if(p-1<=mid) return query(ls[pre],ls[now],p,L,mid);
int res=0;
if(p-1>mid) res=query(rs[pre],rs[now],p,mid+1,R);
if(res!=0) return res;
return query(ls[pre],ls[now],p,L,mid);
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&k);
for(int i=1; i<=n; ++i)
scanf("%d",&a[i]),pos[a[i]]=i;
no=0;
root[0]=build(1,n);
for(int i=1; i<=n; ++i)
root[i]=update(root[i-1],a[i],1,n);
for(int i=1; i<=n; ++i)
{
int l=max(1,pos[i]-k);
int r=min(n,pos[i]+k);
int x=query(root[l-1],root[r],i,1,n);
ans[i]=ans[x]+1;
}
for(int i=1; i<=n; ++i)
printf("%d%c",ans[i],i==n?'\n':' ');
}
return 0;
}
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+10,inf=1e6;
int t,n,m,a[maxn];
int root[maxn],st[maxn*40],ls[maxn*40],rs[maxn*40],no;
int build(int L,int R)
{
int rt=++no;
if(L==R) return rt;
int mid=(L+R)>>1;
ls[rt]=build(L,mid);
rs[rt]=build(mid+1,R);
return rt;
}
int update(int pre,int p,int L,int R)
{
int rt=++no;
st[rt]=st[pre]+1;
ls[rt]=ls[pre];
rs[rt]=rs[pre];
if(L==R) return rt;
int mid=(L+R)>>1;
if(p<=mid) ls[rt]=update(ls[pre],p,L,mid);
if(p>mid) rs[rt]=update(rs[pre],p,mid+1,R);
return rt;
}
int query(int pre,int now,int p,int L,int R)
{
if(st[now]-st[pre]==0) return inf;
if(L==R) return L>=p?L:inf;
int mid=(L+R)>>1;
if(p>mid) return query(rs[pre],rs[now],p,mid+1,R);
int res=inf;
if(p<=mid) res=query(ls[pre],ls[now],p,L,mid);
if(res!=inf) return res;
return query(rs[pre],rs[now],p,mid+1,R);
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
for(int i=1; i<=n; ++i) scanf("%d",&a[i]);
no=0;
root[0]=build(1,n);
for(int i=1; i<=n; ++i)
root[i]=update(root[i-1],a[i],1,n);
int op,t1,t2,t3;
set<int> s;
s.insert(n+1);
int ans=0;
while(m--)
{
scanf("%d",&op);
if(op==1)
{
scanf("%d",&t1);
int pos=ans^t1;
s.insert(a[pos]);
}
else
{
scanf("%d%d",&t2,&t3);
int r=t2^ans;
int k=t3^ans;
int res=query(root[r],root[n],k,1,n);
auto it=s.lower_bound(k);
if(it!=s.end()) ans=min(res,*it);
printf("%d\n",ans);
}
}
}
return 0;
}
HDU - 6703 array (主席树)
链接:http://acm.hdu.edu.cn/showproblem.php?pid=6703
题意:给定一个 n 的排列和 m 个操作,有两种操作:
- 给定pos,使 a p o s + 1 0 7 a_{pos}+10^7 apos+107
- 给定 r、k,找到一个不等于 a i ( 1 ≤ i ≤ r ) a_i(1\le i\le r) ai(1≤i≤r) 的最小的大于等于 k 的数,输出这个数
思路:首先可以想到答案的范围在 [ 1 , n + 1 ] [ 1,n+1] [1,n+1]
- 主席树:在区间 [ r + 1 , n ] [r+1,n] [r+1,n] 上找一个最小的 ≥ k \ge k ≥k的数,同时通过操作 1,相当于把原来的 a i a_i ai 删去了,那么那些被删去的 a i a_i ai 也是可选的。
- set中维护删去的数, n + 1也是可选的
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+10,inf=1e6;
int t,n,m,a[maxn];
int root[maxn],st[maxn*40],ls[maxn*40],rs[maxn*40],no;
int build(int L,int R)
{
int rt=++no;
if(L==R) return rt;
int mid=(L+R)>>1;
ls[rt]=build(L,mid);
rs[rt]=build(mid+1,R);
return rt;
}
int update(int pre,int p,int L,int R)
{
int rt=++no;
st[rt]=st[pre]+1;
ls[rt]=ls[pre];
rs[rt]=rs[pre];
if(L==R) return rt;
int mid=(L+R)>>1;
if(p<=mid) ls[rt]=update(ls[pre],p,L,mid);
if(p>mid) rs[rt]=update(rs[pre],p,mid+1,R);
return rt;
}
int query(int pre,int now,int p,int L,int R)
{
if(st[now]-st[pre]==0) return inf;
if(L==R) return L>=p?L:inf;
int mid=(L+R)>>1;
if(p>mid) return query(rs[pre],rs[now],p,mid+1,R);
int res=inf;
if(p<=mid) res=query(ls[pre],ls[now],p,L,mid);
if(res!=inf) return res;
return query(rs[pre],rs[now],p,mid+1,R);
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
for(int i=1; i<=n; ++i) scanf("%d",&a[i]);
no=0;
root[0]=build(1,n);
for(int i=1; i<=n; ++i)
root[i]=update(root[i-1],a[i],1,n);
int op,t1,t2,t3;
set<int> s;
s.insert(n+1);
int ans=0;
while(m--)
{
scanf("%d",&op);
if(op==1)
{
scanf("%d",&t1);
int pos=ans^t1;
s.insert(a[pos]);
}
else
{
scanf("%d%d",&t2,&t3);
int r=t2^ans;
int k=t3^ans;
int res=query(root[r],root[n],k,1,n);
auto it=s.lower_bound(k);
if(it!=s.end()) ans=min(res,*it);
printf("%d\n",ans);
}
}
}
return 0;
}
2019牛客第九场 H. Cutting Bamboos
链接:https://ac.nowcoder.com/acm/contest/889/H
题意:给定长度为 n 的数组,每个 a i a_i ai 代表竹子的长度,给定询问 ( l,r,x,y ) ,将 [ l , r ] [l,r] [l,r] 的竹子从水平方向砍 y 次,每次砍掉的竹子总长度相同,问砍第 x 次时的高度是多少? ( 1 ≤ n ≤ 2 × 1 0 5 , 1 ≤ q ≤ 1 0 5 ) (1\le n\le 2\times 10^5,1\le q \le 10^5) (1≤n≤2×105,1≤q≤105)
思路:
- 简单分析可得:砍 x 次的总长度为 x ∑ i = l r a i y \frac {x\sum_{i=l}^r a_i}{y} yx∑i=lrai。因此可以二分高度 mid,查询在 [ l , r ] 区间内比 mid 高的竹子颗数和总长度。
- 如果只有一次询问,那么直接 O ( n ) O(n) O(n) 暴力check一遍就好了,复杂度 O ( n l o g 2 h ) O(n log_2h) O(nlog2h) 。但是这里有 q 个询问,必须将check的复杂度降低才行。
- 其实问题是在问 ≥ m i d \ge mid ≥mid 的所有竹子,那么其实可以在主席树上实现这个查询。最终复杂度: O ( q l o g 2 h l o g 2 n ) O(q\ log_2h \ log_2n) O(q log2h log2n)
- 主席树做的就是将无序的排列转为有序,这样才能加速查询。
#include <bits/stdc++.h>
#define fi first
#define se second
#define ll long long
#define pll pair<ll,ll>
using namespace std;
const int maxn=2e5+10,inf=1e7;
const double eps=1e-8;
int n,q;
int a[maxn];
ll pref[maxn];
int l,r,x,y;
int root[maxn],ls[maxn*40],rs[maxn*40],no;
vector<int> allx;
struct Segment
{
int cnt;
ll sum;
}st[maxn*40];
int update(int pre,int p,int L,int R)
{
int rt=++no;
st[rt].cnt=st[pre].cnt+1;
st[rt].sum=st[pre].sum+allx[p-1];
ls[rt]=ls[pre];
rs[rt]=rs[pre];
if(L==R) return rt;
int mid=(L+R)>>1;
if(p<=mid) ls[rt]=update(ls[pre],p,L,mid);
if(p>mid) rs[rt]=update(rs[pre],p,mid+1,R);
return rt;
}
pll query(int pre,int now,int p,int L,int R)
{
if(L==R) return {st[now].cnt-st[pre].cnt,st[now].sum-st[pre].sum};
int mid=(L+R)>>1;
if(p<=mid)
{
pll ans=query(ls[pre],ls[now],p,L,mid);
ans.fi+=st[rs[now]].cnt-st[rs[pre]].cnt;
ans.se+=st[rs[now]].sum-st[rs[pre]].sum;
return ans;
}
else return query(rs[pre],rs[now],p,mid+1,R);
}
pll query2(int pre,int now,int l,int r,int L,int R)
{
if(l<=L&&R<=r)
return {st[now].cnt-st[pre].cnt,st[now].sum-st[pre].sum};
int mid=(L+R)>>1;
pll ans1={0,0},ans2={0,0};
if(l<=mid)
ans1=query2(ls[pre],ls[now],l,r,L,mid);
if(r>mid)
ans2=query2(rs[pre],rs[now],l,r,mid+1,R);
return {ans1.fi+ans2.fi,ans1.se+ans2.se};
}
int main()
{
scanf("%d%d",&n,&q);
for(int i=1;i<=n;++i)
{
scanf("%d",&a[i]);
pref[i]=pref[i-1]+a[i];
allx.push_back(a[i]);
}
sort(allx.begin(),allx.end());
allx.resize(unique(allx.begin(),allx.end())-allx.begin());
int tot=allx.size();
for(int i=1;i<=n;++i)
{
int p=lower_bound(allx.begin(),allx.end(),a[i])-allx.begin()+1;
root[i]=update(root[i-1],p,1,tot);
}
while(q--)
{
scanf("%d%d%d%d",&l,&r,&x,&y);
double L=0,R=allx.back();
double sum=pref[r]-pref[l-1];
double tar=sum/(1.0*y)*x;
while(L+eps<=R)
{
double mid=(L+R)/2;
int p=upper_bound(allx.begin(),allx.end(),mid)-allx.begin()+1;
pll res=query2(root[l-1],root[r],p,tot,1,tot);
if(res.se-res.fi*mid>=tar) L=mid;
else R=mid;
}
cout<<setprecision(8)<<fixed<<L<<"\n";
}
return 0;
}
2019牛客第七场 E. Find the median (线段树)
链接:https://ac.nowcoder.com/acm/contest/887/E
题意:转化后的题意:给定 n 个区间,一个多重集合。一开始集合为空,每次向集合中加入 [ l i , r i ] [l_i,r_i] [li,ri] 中的数,查询每次加入后的中位数。 ( 1 ≤ n ≤ 4 × 1 0 5 , 1 ≤ l i , r i ≤ 1 0 9 ) (1\le n \le 4\times 10^5,1\le l_i,r_i \le 10^9) (1≤n≤4×105,1≤li,ri≤109)
思路:线段树每个叶节点维护当前段的数字个数,根节点维护区间的数字总个数
- 离散化之后,做区间更新,每次查询第 ( s u m + 1 ) / 2 (sum+1)/2 (sum+1)/2 个数
- 类似主席树查询第 k 小一样,根据左右子树数字的个数,找到第 k 个数的位置即可。
#include <bits/stdc++.h>
#define fi first
#define se second
#define ls (rt<<1)
#define rs (rt<<1|1)
#define ll long long
#define int ll
using namespace std;
const int maxn=4e5+10;
int n;
int X1,X2,A1,B1,C1,M1;
int Y1,Y2,A2,B2,C2,M2;
int l[maxn],r[maxn],x[maxn],y[maxn];
vector<int> allx;
ll st[maxn<<3],lazy[maxn<<3];
void pushDown(int rt,int L,int R)
{
if(lazy[rt])
{
int mid=(L+R)>>1;
st[ls]+=(allx[mid]-allx[L-1])*lazy[rt];
st[rs]+=(allx[R]-allx[mid])*lazy[rt];
lazy[ls]+=lazy[rt];
lazy[rs]+=lazy[rt];
lazy[rt]=0;
}
}
void pushUp(int rt)
{
st[rt]=st[ls]+st[rs];
}
void update(int rt,int l,int r,int L,int R)
{
if(l<=L&&R<=r)
{
st[rt]+=allx[R]-allx[L-1];
lazy[rt]+=1;
return;
}
pushDown(rt,L,R);
int mid=(L+R)>>1;
if(l<=mid) update(ls,l,r,L,mid);
if(r>mid) update(rs,l,r,mid+1,R);
pushUp(rt);
}
int query(int rt,int k,int L,int R)
{
if(L==R)
{
int x=st[rt]/(allx[L]-allx[L-1]);
return allx[L-1]+k/x+(k%x?1:0);
}
pushDown(rt,L,R);
int mid=(L+R)>>1;
if(k<=st[ls]) return query(ls,k,L,mid);
else return query(rs,k-st[ls],mid+1,R);
}
main()
{
scanf("%lld",&n);
scanf("%lld%lld%lld%lld%lld%lld",&X1,&X2,&A1,&B1,&C1,&M1);
scanf("%lld%lld%lld%lld%lld%lld",&Y1,&Y2,&A2,&B2,&C2,&M2);
x[1]=X1,x[2]=X2;
y[1]=Y1,y[2]=Y2;
for(int i=3;i<=n;++i)
x[i]=(1ll*A1*x[i-1]+1ll*B1*x[i-2]+C1)%M1;
for(int i=3;i<=n;++i)
y[i]=(1ll*A2*y[i-1]+1ll*B2*y[i-2]+C2)%M2;
for(int i=1;i<=n;++i)
{
int a=min(x[i],y[i])+1;
int b=max(x[i],y[i])+1;
allx.push_back(a-1);
allx.push_back(b);
l[i]=a-1,r[i]=b;
}
sort(allx.begin(),allx.end());
allx.resize(unique(allx.begin(),allx.end())-allx.begin());
int tot=allx.size()-1;
ll sum=0;
for(int i=1;i<=n;++i)
{
sum+=r[i]-l[i];
int p1=lower_bound(allx.begin(),allx.end(),l[i])-allx.begin()+1;
int p2=lower_bound(allx.begin(),allx.end(),r[i])-allx.begin();
update(1,p1,p2,1,tot);
printf("%lld\n",query(1,(sum+1)/2,1,tot));
}
}
2019牛客第七场 C. Governing sand (枚举花费 || 二分 + 树状数组 || 权值线段树)
链接:https://ac.nowcoder.com/acm/contest/887/C
题意:给定一些种类的树,每类树有高度、花费(砍一棵树)、数量三个属性,让你砍掉一些树,使得最高的树的数量大于其它剩余的树的总量,问最少的花费
思路:枚举每一个高度,然后所需的花费就是砍掉比自己高的树,然后再砍掉一些比自己低且花费少的树。
- 注意到花费只有最多只有200,可以计算出比自己高的树的花费,然后枚举花费,从低到高砍树。
- 也可以用树状数组+二分,假设当前需要找到 k 个最小的花费,那么就可以按花费从小到大排序,更新到树状数组上,然后二分位置,找到恰好 ≥ k \ge k ≥k 的位置,统计答案。不过在二分前需要把高度大于等于当前枚举高度的树删去
- 权值线段树:以花费为桶建立权值线段树,区间维护数量和总花费,从低到高枚举高度,查询的是前 k 个的和之后,将当前枚举的树按照花费的大小,更新到权值线段树上。
总结:总体的思路来说,首先枚举高度,从小到大或者从大到小枚举。每次枚举的时候都需要砍掉 k 棵花费最小的树,此时为了快速找到这 k 棵树的总花费,就可以使用权值线段树、树状数组二分。
枚举
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+10;
int n;
struct Node
{
ll h,c,p;
bool operator<(const Node & b) const
{
return h>b.h;
}
}a[maxn];
ll price[210];
int main()
{
while(~scanf("%d",&n))
{
ll sum=0;
for(int i=1;i<=n;++i)
{
ll h,c,p;
scanf("%lld%lld%lld",&h,&c,&p);
a[i]={h,c,p};
price[c]+=p;
sum+=p;
}
sort(a+1,a+1+n);
ll cnt1=0,cnt2=0,cnt3=0;
ll cost1=0,cost2=0,cost3=0;
ll ans=9e18;
for(int i=1;i<=n;++i)
{
int j=i;
cnt2=cost2=0;
while(a[i].h==a[j].h)
{
cnt2+=a[j].p;
price[a[j].c]-=a[j].p;
cost2+=a[j].c*a[j].p;
j++;
}
i=j-1;
cnt3=max(0ll,sum-cnt1-cnt2-(cnt2-1));
ll res=0;
for(int j=1;j<=200;++j)
{
if(cnt3>price[j])
{
cnt3-=price[j];
res+=price[j]*j;
}
else
{
res+=cnt3*j;
cnt3=0;
break;
}
}
cnt1+=cnt2;
ans=min(ans,res+cost1);
cost1+=cost2;
}
printf("%lld\n",ans);
}
return 0;
}
树状数组+二分
#include <bits/stdc++.h>
#define fi first
#define se second
#define ll long long
using namespace std;
const int maxn=1e5+10;
int n;
struct Node
{
ll h,c,num;
bool operator<(const Node & b) const
{
return c<b.c;
}
}a[maxn];
ll c1[maxn],c2[maxn];
int lowbit(int x)
{
return x&(-x);
}
void add(int x,ll v1,ll v2)
{
for(int i=x;i<=n;i+=lowbit(i))
c1[i]+=v1,c2[i]+=v2;
}
ll getsum(int x)
{
ll res=0;
for(int i=x;i>0;i-=lowbit(i))
res+=c1[i];
return res;
}
ll getcost(int x)
{
ll res=0;
for(int i=x;i>0;i-=lowbit(i))
res+=c2[i];
return res;
}
int main()
{
while(~scanf("%d",&n))
{
for(int i=1;i<=n;++i)
{
int h,c,num;
scanf("%lld%lld%lld",&h,&c,&num);
a[i]={h,c,num};
}
sort(a+1,a+1+n);
priority_queue<pair<ll,ll> > pq;
for(int i=1;i<=n;++i)
{
add(i,a[i].num,a[i].num*a[i].c);
pq.push({a[i].h,i});
}
ll ans=4e18,c1=0;
while(!pq.empty())
{
auto t=pq.top();
ll c2=0,c3=0,sum=0;
while(!pq.empty()&&t.fi==pq.top().fi)
{
auto x=pq.top();pq.pop();
int p=x.se;
c3+=getcost(p)-getcost(p-1);
sum+=getsum(p)-getsum(p-1);
add(p,-a[p].num,-a[p].num*a[p].c);
}
ll need=getsum(n)-(sum-1);
if(need>0)
{
int l=1,r=n;
while(l<r)
{
int mid=(l+r)>>1;
if(getsum(mid)<need) l=mid+1;
else r=mid;
}
c2=getcost(l)-(getsum(l)-need)*a[l].c;
}
ans=min(ans,c1+c2);
c1+=c3;
}
printf("%lld\n",ans);
}
return 0;
}
权值线段树
#include <bits/stdc++.h>
#define ls (rt<<1)
#define rs (rt<<1|1)
#define ll long long
using namespace std;
const int maxn=1e5+10;
int n;
ll suff[maxn];
struct Node
{
ll h,c,num;
bool operator<(const Node & b) const
{
return h<b.h;
}
} a[maxn];
struct Node2
{
ll cnt,cost;
}st[210*4];
void update(int rt,int p,int L,int R,int num)
{
if(L==R)
{
st[rt].cnt+=num;
st[rt].cost+=1ll*num*L;
return;
}
int mid=(L+R)>>1;
if(p<=mid) update(ls,p,L,mid,num);
if(p>mid) update(rs,p,mid+1,R,num);
st[rt].cnt=st[ls].cnt+st[rs].cnt;
st[rt].cost=st[ls].cost+st[rs].cost;
}
ll query(int rt,ll k,int L,int R)// k会爆int
{
if(L==R) return 1ll*L*k;
int mid=(L+R)>>1;
if(k<=st[ls].cnt) return query(ls,k,L,mid);
else return st[ls].cost+query(rs,k-st[ls].cnt,mid+1,R);
}
int main()
{
while(~scanf("%d",&n))
{
for(int i=1; i<=n; ++i)
{
ll h,c,num;
scanf("%lld%lld%lld",&h,&c,&num);
a[i]= {h,c,num};
}
sort(a+1,a+1+n);
suff[n+1]=0;
for(int i=n; i>=1; --i) suff[i]=suff[i+1]+a[i].c*a[i].num;
memset(st,0,sizeof(st));
int j;
ll sum1=0,ans=9e18;
for(int i=1; i<=n; i=j)
{
j=i;
ll sum2=0;
while(a[i].h==a[j].h)
{
sum2+=a[j].num;
j++;
}
ll k=max(0ll,sum1-(sum2-1));
ll cost1=query(1,k,1,200);
ans=min(ans,cost1+suff[j]);
for(int x=i;x<j;++x)
update(1,a[x].c,1,200,a[x].num);
sum1+=sum2;
}
printf("%lld\n",ans);
}
return 0;
}