T1:Star Way To Heaven
做多了这种题很容易第一眼想到二分距离然后看上下是否连通,但是6000的n2log显然会T(我信仰了一发之后自己造大数据连1000都要跑3s+。。)
再仔细思考一下,答案需要满足小于这个距离的边不能使上下连通,那么至少需要多大的边才能使上下连通呢?最小生成树!上连到下经过的最大边就是我们的答案。O(n2)Prim即可。
这启示我们最小值最大除了二分还可以最小生成树。
T2:God Knows
如果把
p
[
i
]
p[i]
p[i]看做高度,最后答案删除的点一定是一个极长上升序列(任意两个
i
<
j
i<j
i<j之间不存在
p
[
i
]
<
p
[
k
]
<
p
[
j
]
p[i]<p[k]<p[j]
p[i]<p[k]<p[j])
那么就有了一个 O ( n 2 ) O(n^2) O(n2)的DP, f [ i ] f[i] f[i]表示最后一个选的是 i i i的最小代价,枚举 j j j来转移, j j j要满足 p [ j ] < p [ i ] p[j]<p[i] p[j]<p[i]且中间不存在介于 p [ j ] p[j] p[j]到 p [ i ] p[i] p[i]之间的高度,这可以通过从大到小枚举 k k k,记录对应的 p [ k ] < p [ i ] p[k]<p[i] p[k]<p[i]的最大值 m x mx mx,如果 m x < p [ j ] < p [ i ] mx<p[j]<p[i] mx<p[j]<p[i]则可以转移。
如果用线段树位置对应序列下标来维护转移,需要满足两个条件,并不好做。我们改为用线段树位置对应 p p p,线段树节点的值对应序列下标,那么区间 [ 1 , p [ i ] − 1 ] [1,p[i]-1] [1,p[i]−1]中值为后缀最大值的点即可作为转移点(p>这个点,则下标一定比这个点小)
好题。
Code:
#include<bits/stdc++.h>
#define maxn 200005
using namespace std;
const int inf = 2e9+1;
int n,p[maxn],c[maxn],mx[maxn<<2],val[maxn<<2],vl[maxn<<2];
int calc(int i,int l,int r,int d){
if(l==r) return mx[i]>d?val[i]:inf;
int mid=(l+r)>>1;
if(mx[i<<1|1]<=d) return calc(i<<1,l,mid,d);
else return min(vl[i],calc(i<<1|1,mid+1,r,d));
}
void insert(int i,int l,int r,int p,int id,int v){
if(l==r) {mx[i]=id,val[i]=v;return;}
int mid=(l+r)>>1;
if(p<=mid) insert(i<<1,l,mid,p,id,v);
else insert(i<<1|1,mid+1,r,p,id,v);
mx[i]=max(mx[i<<1],mx[i<<1|1]),vl[i]=calc(i<<1,l,mid,mx[i<<1|1]);
}
int id,v;
void query(int i,int l,int r,int p){
if(r<=p) {v=min(v,calc(i,l,r,id)),id=max(id,mx[i]);return;}
int mid=(l+r)>>1;
if(mid<p) query(i<<1|1,mid+1,r,p);
query(i<<1,l,mid,p);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&p[i]);
for(int i=1;i<=n;i++) scanf("%d",&c[i]);
for(int i=1;i<=n*4;i++) vl[i]=inf;
for(int i=1;i<=n;i++){
id=0,v=inf,query(1,1,n,p[i]);
insert(1,1,n,p[i],i,(v<inf?v:0)+c[i]);
}
id=0,v=inf,query(1,1,n,n);
printf("%d\n",v);
}
T3:Lost My Music
把
c
v
−
c
u
d
e
p
u
−
d
e
p
v
c_v-c_u\over {dep_u-dep_v}
depu−depvcv−cu乘以-1就变成了斜率的形式,求原数的最小值即求斜率的最大值,于是问题变成了维护祖先的凸包,又因为dep单增,所以想维护祖先的单调栈。
dfs时暴力弹栈可能被卡成O(n2),我们需要可持久化栈。
因为当前点与凸包上的点的斜率是有单调性的,所以我们可以二分凸包上的祖先,栈都可以不用存,直接用倍增数组实现,点这里有更详细的解析。
Code:
#include<bits/stdc++.h>
#define maxn 500005
using namespace std;
char cb[1<<18],*cs,*ct;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<18,stdin),cs==ct)?0:*cs++)
inline void read(int &a){
char c;while(!isdigit(c=getc()));
for(a=c-'0';isdigit(c=getc());a=a*10+c-'0');
}
const int Log = 18;
int n,c[maxn],dep[maxn],fa[maxn],f[maxn][Log+1];
int fir[maxn],nxt[maxn],to[maxn],tot;
inline void line(int x,int y){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y;}
inline bool check(int k,int j,int i){return 1ll*(c[i]-c[k])*(dep[i]-dep[j])>=1ll*(c[i]-c[j])*(dep[i]-dep[k]);}
void dfs(int u){
dep[u]=dep[fa[u]]+1;
int x=fa[u],t;
for(int i=Log;i>=0;i--){//二分凸包祖先
if((t=f[x][i])<2) continue;
if(check(f[t][0],t,u)) x=t;
}
if(x!=1&&check(f[x][0],x,u)) x=f[x][0];
f[u][0]=x;
for(int i=1;i<=Log;i++) f[u][i]=f[f[u][i-1]][i-1];
for(int i=fir[u];i;i=nxt[i]) dfs(to[i]);
}
int main()
{
read(n);
for(int i=1;i<=n;i++) read(c[i]);
for(int i=2;i<=n;i++) read(fa[i]),line(fa[i],i);
dfs(1);
for(int i=2;i<=n;i++) printf("%.10f\n",(c[f[i][0]]-c[i])*1./(dep[i]-dep[f[i][0]]));
//最优的点就是凸包上的前一个点
}
鉴于这道题只需要求一个答案,并不需要保存倍增数组,我们可以在dfs时将凸包存入栈中,然后二分当前点应该插入到栈的哪个位置,直接令那个位置=当前点,然后递归儿子,因为只改动了一个点,所以存下那个位置原来的数,退出dfs时将其还原即可。
其实就是利用凸包的单调性将暴力弹栈改为二分弹栈位置。