败者死于绝望,胜者死于渴望。c++
一看这个题就来者不善,对于第一题第一眼觉得是一个大模拟,没想到是最小生成树。数组
对于第二题,先是看到了状压能够搞到的 20pts 而后对着暴力一顿猛调后来发现是题面理解错了。优化
最后 40min 的时候仍是把目光投向了这个题的另外一个部分分,而后搞了一个区间 DP 可是应该是 线性 DP。spa
而后我就又凉了,第三题第一眼是输,第二眼是 DFS 序,第三眼是 DFS 序上的线段树,再而后就老老实实去整暴力了。code
后来题目名字在网上一搜,居然都是歌名。。blog
正解是最小生成树,把上下边界看成两个点来看,而后就搞各个点之间的距离再用 Prim 求最小生成树了。get
注意一点,这里用 Kruskal会 TLE 由于多了一个 \(logn\) 的复杂度。qt
下面主要证实一下最小生成树解法的正确性。it
好比下面的这个图ast
全部最小边权的边所链接的就好似链接了上下两个边界的一个阻断线,显然,咱们是必定要从其中穿过去的。
那么,咱们必定要选择其中权值最大的边的中点穿过。
所以,答案就是最小生成树上最大边权的一半
#include<bits/stdc++.h> #define int long long #define ls x<<1 #define rs x<<1|1 using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while(ch>'9'||ch<'0') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } const int N=6e3+10,INF=1e18; int n,m,tot; bool vis[N]; double ans,dis[N]; struct Node { int x,y; }s[N]; double dist(double x,double y,double x2,double y2) { return sqrt((x-x2)*(x-x2)+(y-y2)*(y-y2)); } signed main() { n=read(); m=read(); tot=read(); for(int i=1;i<=tot;i++) { s[i].x=read(); s[i].y=read(); dis[i]=s[i].y; } dis[tot+1]=m; for(int i=1;i<=tot+1;i++) { int pos=0; for(int j=1;j<=tot+1;j++) if(!vis[j]&&(!pos||dis[j]<dis[pos])) pos=j; vis[pos]=true; ans=max(ans,dis[pos]); if(pos==tot+1) break; for(int j=1;j<=tot;j++) if(!vis[j]) dis[j]=min(dis[j],dist(s[pos].x,s[pos].y,s[j].x,s[j].y)); dis[tot+1]=min(dis[tot+1],1.0*m-s[pos].y); } printf("%.10lf",ans/2); return 0; }
本题的思路或许有一点难懂,就是那种只可意会不可言传的感受。
首先要明白一个概念:极长上升序列。(对于以后的点都不能够比这个点大)
再看一下 40pts 的作法,直接暴力 DP 设 \(f_i\) 数组表示以 i 结尾的极长上升序列的价值。
而后,先初始化一下每一个序列的开始,须要知足以前全部的点的值都小于它。
接下来在枚举结尾点的前提下,一个一个向前跳,要知足从该节点一直到 i-1 节点没有比该节点还要大的点。
其实就是为了防止隔级跳的状况。
在寻找结尾节点的时候和寻找起始节点的相反(保证 i 到 n 没有比它大的点)
而后咱们就获得了暴力DP的\(code\)
可是 \(n^2\) 的彷佛太慢了,因而咱们就能够考虑线段树优化。
好像是相似于一种叫作李超线段树的东西,具体实现细节见代码
#include<bits/stdc++.h> #define int long long #define ls x<<1 #define rs x<<1|1 #define f() cout<<"Fuck"<<endl; using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while(ch>'9'||ch<'0') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } const int N=2e5+10,INF=1e18;//v为新的权值 int n,las,v,s[N],val[N],q[N<<2];//q数组记录以前的最有解 struct Segment_Tree { int las,dat;//dat就是前面的 f 数组而且必须以这个节点结尾,las表示每一个的最后的那个点 }tre[N<<2];//las数组表示当前节点所在的极长序列的末尾 int solve(int x,int l,int r,int pos) { if(l==r) return (tre[x].las>pos)?tre[x].dat:INF; int mid=(l+r)>>1; if(tre[rs].las<=pos) return solve(ls,l,mid,pos);//不符合直接左儿子 return min(q[x],solve(rs,mid+1,r,pos));//左儿子的部分必定是极长上升序列的部分 } void push_up(int x,int l,int r) { int mid=(l+r)>>1; tre[x].las=max(tre[ls].las,tre[rs].las); q[x]=solve(ls,l,mid,tre[rs].las); } void insert(int x,int l,int r,int pos,int num,int vall) { if(l==r) { tre[x].dat=vall; tre[x].las=num; return; } int mid=(l+r)>>1; if(pos<=mid) insert(ls,l,mid,pos,num,vall); else insert(rs,mid+1,r,pos,num,vall); push_up(x,l,r); } void query(int x,int l,int r,int pos) { if(r<=pos) { v=min(v,solve(x,l,r,las));//只要在这个点以前就查询最小的价值 las=max(las,tre[x].las); return ; } int mid=(l+r)>>1; if(mid<pos) query(rs,mid+1,r,pos); query(ls,l,mid,pos);//至关于向前跳的一个过程 } signed main() { n=read(); for(int i=1;i<=n;i++) s[i]=read(); for(int i=1;i<=n;i++) val[i]=read(); memset(q,0x7f,sizeof(q)); for(int i=1;i<=n;i++) { las=0; v=INF; query(1,1,n,s[i]);//求值储存到v而且对于前面的进行更新 if(v>=INF) v=0; insert(1,1,n,s[i],i,v+val[i]); } las=0; v=INF; query(1,1,n,n); printf("%lld",v); return 0; }
这个题的第一思路仍是在树上维护某些东西,可是能想到的最优的也就只是在每条链上跳了。
可是在链上跳能够得到 50pts 的巨额分数(前提是你不和我同样开小数组)
正解就是什么可持久化栈维护凸包。
But,在我颓了别的题解以后发现这并无什么用。
直接在树上用倍增维护凸包就很是的棒,嗯,就很棒。
对于下图,显然咱们应该维护一个下凸包,对于 D 点的解从 C 点转移比从以前任意一点转移都要更优。
如今举 C 和 B 点转移来对比也就是:\(\dfrac{c_D-c_B}{dep_D-dep_B}\ge \dfrac{c_D-c_C}{dep_D-dep_C}\) 就能够进行更新。
再次基础上优化倍增就行了。
#include<bits/stdc++.h> #define int long long using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while(ch>'9'||ch<'0') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } const int N=5e5+10,INF=1e18; int n,s[N],fa[N],dep[N],f[N][25]; int tot,ver[N],head[N],nxt[N]; double ans[N]; inline void add_edge(int x,int y) { ver[++tot]=y; nxt[tot]=head[x]; head[x]=tot; } bool judge(int x,int y,int z) { return (1.0*s[z]-1.0*s[x])*(1.0*dep[z]-1.0*dep[y])>=(1.0*s[z]-1.0*s[y])*(1.0*dep[z]-1.0*dep[x]); } void dfs(int x) { dep[x]=dep[fa[x]]+1; int pos=fa[x]; for(int i=20;i>=0;i--) { int temp=f[pos][i]; if(temp<=1) continue; if(judge(f[temp][0],temp,x)) pos=temp; } if(pos!=1&&judge(f[pos][0],pos,x)) pos=f[pos][0]; f[x][0]=pos; for(int i=0;f[x][i];i++) f[x][i+1]=f[f[x][i]][i]; for(int i=head[x];i;i=nxt[i]) dfs(ver[i]); } signed main() { n=read(); for(int i=1;i<=n;i++) s[i]=read(); for(int i=2;i<=n;i++) { fa[i]=read(); add_edge(fa[i],i); } dfs(1); for(int i=2;i<=n;i++) printf("%.10lf\n", (1.0*s[f[i][0]]-1.0*s[i])/(1.0*dep[i]-dep[f[i][0]]) ); return 0; }