HGOI 20191030am 题解
Problem A 腿部挂件
给出$n$个数的序列$a_i$,支持$T$次操作。
每次操作形如$x , l , r$,计算$\max_{i = l}^{r} (a_i \oplus x)$的值。
对于$100\%$的数据满足$1 \leq n \leq 2 \times 10^5 , 0 \leq a_i \leq 10^9$
Solution :
通常可以使用可持久化字典树求解,但是这里可以用字典树套vector来做。
这样当当前节点vector有一个在$l,r$的数中就往下走,还是按照以前的贪心策略,从高位到低位贪心。
这样做的时间复杂度是$O(n {log_2} ^2 n)$
#include<bits/stdc++.h> #define pb push_back using namespace std; struct node { vector<int>p; int ls,rs; node(){ls=rs=-1;} }tr[6100000]; int n,m,a,x,y,cnt,o[50]; void push(int k,int p) { for(int i=30-1,nw=1;i>=0;i--) { if(k&o[i]) { if(tr[nw].ls==-1)tr[nw].ls=++cnt; nw=tr[nw].ls; } else { if(tr[nw].rs==-1)tr[nw].rs=++cnt; nw=tr[nw].rs; } tr[nw].p.pb(p); } } int query(int k,int l,int r) { int ans=0; for(int i=30-1,nw=1;i>=0;i--) { if(k&o[i]) { if(tr[nw].rs==-1||*(tr[tr[nw].rs].p.end()-1)<l||*lower_bound(tr[tr[nw].rs].p.begin(),tr[tr[nw].rs].p.end(),l)>r) nw=tr[nw].ls,ans+=o[i]; else nw=tr[nw].rs; } else { if(tr[nw].ls==-1||*(tr[tr[nw].ls].p.end()-1)<l||*lower_bound(tr[tr[nw].ls].p.begin(),tr[tr[nw].ls].p.end(),l)>r) nw=tr[nw].rs; else nw=tr[nw].ls,ans+=o[i]; } } return ans^k; } int main() { freopen("hugclose.in","r",stdin); freopen("hugclose.out","w",stdout); scanf("%d%d",&n,&m),o[0]=cnt=1; for(int i=1;i<30;i++)o[i]=o[i-1]*2; for(int i=1;i<=n;i++)scanf("%d",&a),push(a,i); for(int i=1;i<=m;i++)scanf("%d%d%d",&a,&x,&y),printf("%d\n",query(a,x+1,y+1)); return 0; }
Problem B 走夜路
一条直线上有n+1个充电站,一开始你在第一个充电站,每个充电站都能以某个代价充电
每走一个单位距离就要耗费一单位电,求按顺序走完所有充电站的最小代价
对于$100\%$的数据满足$1 \leq n \leq 5\times 10^5$
Solution :
考虑一种贪心,依次考虑每一个充电站。
下一个充电站优先级从高到低依次是:
- 当前充电站以后第一个费用小于当前充电站(此时,直接在当前充电站充满恰好能到下个充电站的电,然后直接走到那个充电站即可)
- 终点(如果当前充电站冲一定的点之后能到终点,那么就直接到达终点)
- 在充满电后能走到的所有充电站中,费用最小的充电站(此时需要先在当前充电站充满电后再移动)
可以考虑使用单调栈来求对于每个位置右侧第一个小于它的数,(从前往后加入数,满足栈单调增,弹栈的时候记录即可)
然后再使用小常数的$st$表来求出静态区间最小值并且维护标号。
所以,本题就是$O(n log_2 n)$的复杂度了。
#pragma GCC optimize(3) # include <bits/stdc++.h> using namespace std; const int N=500000+10; int st[1<<20][20],id[1<<20][20]; # define int long long int n,T,d[N],a[N],r[N]; stack<int>s; int dist(int x,int y) { if (x>y) swap(x,y); return d[y-1]-d[x-1];} namespace fast_IO{ const int IN_LEN = 10000000, OUT_LEN = 10000000; char ibuf[IN_LEN], obuf[OUT_LEN], *ih = ibuf + IN_LEN, *oh = obuf, *lastin = ibuf + IN_LEN, *lastout = obuf + OUT_LEN - 1; inline char getchar_(){return (ih == lastin) && (lastin = (ih = ibuf) + fread(ibuf, 1, IN_LEN, stdin), ih == lastin) ? EOF : *ih++;} inline void putchar_(const char x){if(oh == lastout) fwrite(obuf, 1, oh - obuf, stdout), oh = obuf; *oh ++= x;} inline void flush(){fwrite(obuf, 1, oh - obuf, stdout);} int read(){ int x = 0; int zf = 1; char ch = ' '; while (ch != '-' && (ch < '0' || ch > '9')) ch = getchar_(); if (ch == '-') zf = -1, ch = getchar_(); while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar_(); return x * zf; } void write(int x){ if (x < 0) putchar_('-'), x = -x; if (x > 9) write(x / 10); putchar_(x % 10 + '0'); } } using namespace fast_IO; void build() { memset(st,0x3f,sizeof(st)); for (int i=1;i<=n;i++) st[i][0]=a[i],id[i][0]=i; for (int i=1;i<=19;i++) for (int j=1;j<=n;j++) if (st[j][i-1]<st[j+(1<<(i-1))][i-1]) { st[j][i] = st[j][i-1]; id[j][i] = id[j][i-1]; } else { st[j][i] = st[j+(1<<(i-1))][i-1]; id[j][i] = id[j+(1<<(i-1))][i-1]; } } int LOG[N]; int query(int l,int r) { int k=LOG[r-l+1]; if (st[l][k] < st[r-(1<<k)+1][k]) return id[l][k]; else return id[r-(1<<k)+1][k]; } signed main() { n=read();T=read(); for (int i=1;i<=n;i++) LOG[i] = log2(i); for (int i=1;i<=n;i++) d[i]=read(),a[i]=read(); build(); while (!s.empty()) s.pop(); for (int i=n;i>=1;i--) { while (!s.empty()&&a[s.top()]>=a[i]) s.pop(); if (s.empty()) r[i]=n; else r[i]=s.top()-1; s.push(i); } for (int i=1;i<=n;i++) r[i] = (r[i]==n)?(-1):(r[i]+1), d[i] += d[i-1]; int now = 1, res = 0; int cost = 0; while (true) { if (r[now]!=-1 && dist(r[now] , now) <= T) { if (res >= dist(r[now], now)) res-=dist(r[now],now); else cost += 1ll * a[now]*(dist(r[now], now)-res),res = 0; now = r[now]; } else if (dist(n+1,now) <= T) { if (res >= dist(n+1, now)) res-=dist(n+1,now); else cost += 1ll * a[now]*(dist(n+1,now)-res),res = 0; break; } else { cost += 1ll * (T-res)*a[now]; int l = now+1, r = n , ans = -1; while (l<=r) { int mid = (l+r) >> 1; if (dist(mid,now)<=T) ans=mid,l=mid+1; else r = mid-1; } if (ans == -1) { write(-1); flush(); return 0; } int to = query(now+1,ans); res = T - dist(to , now); now = to; } } write(cost); flush(); return 0; }
Problem C 宝石专家
给出$n$个数的序列$a_i$,处理$T$个询问,
每个询问形如$l,r$,请输出在$a_l ... a_r$中两个相同的数的最近距离。
若$a_x = a_y$,那么距离定义为$|x- y|$
对于$100\%$的数据,满足$1 \leq n,T\leq 2 \times 10^5$
Solution :
首先,我们发现对于相同的数,在一些位置,只有相邻的两个数所构成的线段有意义。
所以需要处理的小线段长度在$n$这个级别。
我们考虑将操作离线,放在一个$[1,n]$的数轴上,并考虑从$n$到$1$依次扫。
如果当前遇到了一个小线段的左端点$l$,那么在线段树中$[r,n]$用这条线段的权值$r-l$来更新答案。
所以,线段树的意义是扫到当前位置,询问线段的右端点的答案。
所以,如果遇到一个询问线段,那么直接求出$[l,r]$的最小值即可,但是由于值的单调性,直接单点求出$r$的值即可。
时间复杂度就是$O(n log_2 n)$。
#pragma GCC optimize(3) # include <bits/stdc++.h> # define inf (0x3f3f3f3f) using namespace std; const int N = 2e5 + 10; vector<int>tmp; vector<int>v[N]; vector< pair<int,int> >r[N],qes[N]; int n,m,a[N],ans[N]; namespace fast_IO{ const int IN_LEN = 10000000, OUT_LEN = 10000000; char ibuf[IN_LEN], obuf[OUT_LEN], *ih = ibuf + IN_LEN, *oh = obuf, *lastin = ibuf + IN_LEN, *lastout = obuf + OUT_LEN - 1; inline char getchar_(){return (ih == lastin) && (lastin = (ih = ibuf) + fread(ibuf, 1, IN_LEN, stdin), ih == lastin) ? EOF : *ih++;} inline void putchar_(const char x){if(oh == lastout) fwrite(obuf, 1, oh - obuf, stdout), oh = obuf; *oh ++= x;} inline void flush(){fwrite(obuf, 1, oh - obuf, stdout);} int read(){ int x = 0; int zf = 1; char ch = ' '; while (ch != '-' && (ch < '0' || ch > '9')) ch = getchar_(); if (ch == '-') zf = -1, ch = getchar_(); while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar_(); return x * zf; } void write(int x){ if (x < 0) putchar_('-'), x = -x; if (x > 9) write(x / 10); putchar_(x % 10 + '0'); } } using namespace fast_IO; struct Seg { int val,tag; Seg() { val = tag = inf;} }tr[N<<2]; # define lson ls,l,mid # define rson rs,mid+1,r # define mid (l+r>>1) # define ls (x<<1) # define rs (x<<1|1) void down(int x) { tr[ls].val=min(tr[ls].val,tr[x].tag); tr[rs].val=min(tr[rs].val,tr[x].tag); tr[ls].tag=min(tr[ls].tag,tr[x].tag); tr[rs].tag=min(tr[rs].tag,tr[x].tag); tr[x].tag = inf; } void update(int x,int l,int r,int opl,int opr,int val) { if (opl <= l && r <= opr) { tr[x].tag = min(tr[x].tag , val); tr[x].val = min(tr[x].val , val); return; } down(x); if (opl<=mid) update(lson,opl,opr,val); if (opr> mid) update(rson,opl,opr,val); tr[x].val = min(tr[ls].val , tr[rs].val); } int query(int x,int l,int r,int pos) { if (l == r) return tr[x].val; down(x); if (pos<=mid) return query(lson,pos); else return query(rson,pos); } int main() { n=read();m=read(); for (int i=1;i<=n;i++) { tmp.push_back(a[i]=read()); } sort(tmp.begin(),tmp.end()); tmp.erase(unique(tmp.begin(),tmp.end()),tmp.end()); for (int i=1;i<=n;i++) { a[i] = lower_bound(tmp.begin(),tmp.end(),a[i]) - tmp.begin()+1; v[a[i]].push_back(i); } for (int i=1;i<=n;i++) { if (v[i].size() < 2) continue; for (int j=0;j<v[i].size()-1;j++) r[v[i][j]].push_back(make_pair(v[i][j+1],v[i][j+1]-v[i][j])); } for (int i=1;i<=m;i++) { int l=read(),r=read(); qes[l].push_back(make_pair(r,i)); } for (int i=n;i>=1;i--) { int sz = r[i].size(); if (sz) { for (int j=0;j<sz;j++) update(1,1,n,r[i][j].first,n,r[i][j].second); } sz = qes[i].size(); if (sz) { for (int j=0;j<sz;j++) ans[qes[i][j].second] = query(1,1,n,qes[i][j].first); } } for (int i=1;i<=m;i++) { write((ans[i]>=inf)?-1:ans[i]); putchar_('\n'); } flush(); return 0; }