8月19日考试 题解(堆+二分答案+树状数组+反悔贪心)
今天没能做出来一道题,但是部分分给的比较良心。继续加油吧。
T1 climb
题目大意:给定一棵含有$n$个结点的树,每条边有边权,根节点为$1$。对于某些结点,可以直接到达深度小于等于它的结点,花费为$(dep[x]-dep[to])*k$。问每个结点到根节点的最小代价。
考试的时候就差最后一步,没能把$O(n)$优化成$O(\log n)$,挺可惜的。
我们先遍历整棵树,把每个结点的深度和到根节点的距离求出来,分别记为$dep$和$dis$。
然后我们按照深度遍历结点,对于一个结点,它的答案有两种情况:
1.$dis[fa]+edge[i]$
2.找到深度小于等于它的且最小的$dis$,此时有$dis[to]+(dep[i]-dep[to])*k$。
对于第二种情况,我们自然可以用堆来维护这个最小的$dis$。时间复杂度$O(n\log n)$。
实际上我们可以先将边权减去$k$,这样在过程种可以省很多事,最后输出的时候在加上$dep*k$即可。(不知道为什么我的代码不这样操作会出锅……
代码:
#include<bits/stdc++.h> using namespace std; const int maxn=500005; const int inf=1e9; int n,k,dep[maxn],dis[maxn],edge[maxn],fa[maxn],maxx; bool ban[maxn]; struct node { int pos,dis; bool operator < (const node &x) const{return x.dis<dis;} };priority_queue<node> q; vector<int> v[maxn]; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } int main() { n=read();k=read(); dep[1]=0;dis[1]=0; for (int i=2;i<=n;i++) { int f=read(),w=read();ban[i]=read();w-=k; fa[i]=f;dep[i]=dep[f]+1;dis[i]=dis[f]+w;edge[i]=w; v[dep[i]].push_back(i);maxx=max(maxx,dep[i]); } q.push((node){1,dis[1]}); for (int i=1;i<=maxx;i++) { int minn=inf,x=q.top().dis; for (int j=0;j<v[i].size();j++) { int now=v[i][j]; dis[now]=min(dis[now],edge[now]+dis[fa[now]]); if (!ban[now]) dis[now]=min(dis[now],x); minn=min(minn,dis[now]); } for (int j=0;j<v[i].size();j++) { if (!ban[v[i][j]]) dis[v[i][j]]=min(dis[v[i][j]],minn); q.push((node){v[i][j],dis[v[i][j]]}); } } for (int i=1;i<=n;i++) printf("%d\n",dis[i]+dep[i]*k); return 0; }
T2 h
题目大意:数轴上有$n$个点,它们的初始位置为$p$,移动速度为$v$。一些点可能在移动过程中相遇,如果可能相遇,那么这些时刻显然有一个是最小的。现在你可以删去$k$个点,然后求出最小的相遇时刻。如果不可能相遇输出"Forever"。
考试的时候没想到二分答案,纯粹是靠着良心的部分分骗了60分?(雾
我们先把点按$p$为关键字进行排序。将$t$时刻时它们的位置存入一个新数组中,记为$b$。开始时对于两个位置$i<j$,均有$a_i<a_j$;如果$b_i>b_j$,那么它们将会碰撞。
为了避免这种情况,我们要在$b$数组中把$i<j$且$b_i>b_j$的情况删去。也就是说我们要尽可能少地让$b$成为最长上升子序列。
于是我们可以用树状数组求最长上升子序列。时间复杂度$O(n\log^2 n)$。
代码:
#include<bits/stdc++.h> #define int long long using namespace std; const double eps=1E-8; const double inf=1E12; const int maxn=50005; int tree[maxn],n,k; struct node { double p,v; int id; bool operator < (const node &x) const{return p<x.p;} }a[maxn],b[maxn]; inline int lowbit(int x){return x&(-x);} bool cmp(node x,node y){return x.p<y.p;} inline void add(int x,int y) { while(x<=n) { tree[x]=max(tree[x],y); x+=lowbit(x); } } inline int query(int x) { int res=0; while(x>0) { res=max(res,tree[x]); x-=lowbit(x); } return res; } inline bool check(double t) { memset(tree,0,sizeof(tree)); for (int i=1;i<=n;i++) b[i].id=i,b[i].p=a[i].p+a[i].v*t; sort(b+1,b+n+1); for (int i=1;i<=n;i++) a[b[i].id].id=i; for (int i=1;i<=n;i++) { int x=a[i].id; int y=query(x-1); add(x,y+1); } return n-query(n)<=k; } signed main() { ios::sync_with_stdio(false); cin>>n>>k; for (int i=1;i<=n;i++) cin>>a[i].p>>a[i].v; sort(a+1,a+n+1); double l=0,r=inf,mid; while(abs(l-r)>eps) { mid=(l+r)/2; if (check(mid)) l=mid; else r=mid; } if (abs(mid-inf)<=eps) cout<<"Forever"; else cout<<fixed<<setprecision(4)<<mid; return 0; }
T3 pancake
题目大意:给定一个长度为$n$的序列,有$q$次询问。你可以将每个序列划分成$b_{1,1},b_{1,2},\cdots ,b_{1,k_i}$,但要求$\sum\limits_{i=1}^n k_i=k$且$\sum\limits_{i=1}^n \sum\limits_{j=1}^{k_i} b_{i,j}$最小。每次询问是以下三种操作之一:1.$k+=1$ 2.$k-=1$ 3.$a[++n]=x$。对于一开始的序列和每一次询问,都需要输出这个最小值。
暴力乱搞混了25分。正解实在很妙,确实没想到。
可以发现,一个数划分的越平均越好。可以证明:
假设$a\geq b$
$(a^2+b^2)-(a+1)^2-(b-1)^2=2(b-a)-2<0$
所以划分的数的差值不超过$1$。
注意到对于同一个长度$len$,如果切的刀数越多,那么代价就越小,并且代价减小的速度是越来越慢的。令$cost(len,k)$表示长度为$len$的数切$k$刀的代价,那么$cost(len,k+1)-cost(len,k)$是单调上升且小于$0$的。
这样我们可以维护两个multiset,一个用来插入,一个用来删除。每次找到$cost(len,k+1)-cost(len,k)$的最小的那个值,加上它,然后删除;再在用来插入的set插入$cost(len,k+2)-cost(len,k+1)$,在用来删除的set插入$cost(len,k)-cost(len,k+1)$。删除操作也是一样的。这样我们就可以在$\log n$时间完成操作1和操作2。
对于操作3,我们其实可以暴力改。把上述的差分想象成一个表格,就相当于我们让别的地方少占一个格子,然后看新加入的数的这一列能多添加几个格子,直到答案最优为止。可以证明总操作数是$n\log n$级别的。
具体实现可以看代码。时间复杂度$O(n\log^2 n)$。
代码:
#include<bits/stdc++.h> #define int long long using namespace std; const int maxn=1000005; const int inf=8e18; int k[maxn],a[maxn],ans,n,q,cut,opt;//k:number struct node { int num,w; node(int a=0,int b=0):num(a),w(b){} bool operator < (const node &x) const{return w==x.w?num<x.num:w<x.w;} }; multiset<node> s0,s1;//0:add;1:del inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } inline int get(int x,int k) { if (x<k||k==0) return inf; int base=x/k; return base*base*(k-x%k)+(base+1)*(base+1)*(x%k); } inline void add() { multiset<node>::iterator p=s0.begin(); node A=*p; ans+=A.w; int id=A.num; s1.erase(s1.find(node(id,get(a[id],k[id]-1)-get(a[id],k[id])))); s0.erase(p); k[id]++; s0.insert(node(id,get(a[id],k[id]+1)-get(a[id],k[id]))); s1.insert(node(id,get(a[id],k[id]-1)-get(a[id],k[id]))); } inline void del() { multiset<node>::iterator p=s1.begin(); node A=*p; ans+=A.w; int id=A.num; s0.erase(s0.find(node(id,get(a[id],k[id]+1)-get(a[id],k[id])))); s1.erase(p); k[id]--; s0.insert(node(id,get(a[id],k[id]+1)-get(a[id],k[id]))); s1.insert(node(id,get(a[id],k[id]-1)-get(a[id],k[id]))); } signed main() { n=read();q=read();cut=read(); for (int i=1;i<=n;i++) { a[i]=read(); ans+=a[i]*a[i]; k[i]=1; s0.insert(node(i,get(a[i],2)-get(a[i],1))); s1.insert(node(i,get(a[i],0)-get(a[i],1))); } for (int i=1;i<=cut;i++) add(); printf("%lld\n",ans); while(q--) { opt=read(); if (opt==1) add(); if (opt==2) del(); if (opt==3) { a[++n]=read(); ans+=a[n]*a[n]; k[n]=1; s0.insert(node(n,get(a[n],2)-get(a[n],1))); s1.insert(node(n,get(a[n],0)-get(a[n],1))); int last=ans; while(1) { del(); add(); if (ans==last) break; last=ans; } } printf("%lld\n",ans); } return 0; }