恩欧挨批模拟试题-16

水博客太快乐了

RT

考场

挂了。

分数

也挂了。

题解

A. Star Way To Heaven

一开始以为这题是和这首神曲有关的,于是推测其他两题也是如此,搜后发现后两首歌出自同一张专辑。。。
然后发现 \(star\) \(way\) \(to\) \(heaven\) 是这张专辑第一首歌。。。。
大意了。。。
这就去追《凉宫春日的忧郁》。。。

说完废话终于开始写题解了。
这题乍一看就是个二分答案啊!!

所以考虑二分答案后该怎么 \(check\)
若从上到下有些星星连成一个屏障的话,那么显然从左到右无法连通,所以只要找到这个最小的屏障的最大值即可。
这么看貌似不需要二分答案,直接贪心找到最小的屏障即可。
所以这题是贪心,写下来像个 \(prim\) ,但和 \(prim\) 没什么关系,就是贪心。。。。

时间复杂度 \(O(k^{2})\)

code
#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
const int N=6010;
inline int read(){
	int f=1, x=0; char ch=getchar();
	while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
	while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
	return f*x;
}
int n, m, k;
double ans, dis[N];
pair<double, double > d[N];
bool vis[N];
struct node{
	double dis;
	int id;
	friend bool operator < (const node &x, const node &y) { return x.dis > y.dis; }
};
priority_queue<node> q;
double dist(int a, int b) { return sqrt((d[a].x-d[b].x)*(d[a].x-d[b].x)+(d[a].y-d[b].y)*(d[a].y-d[b].y)); }
inline void prim(){
	for(int i=1; i<=k; ++i) q.push((node){m-d[i].y, i}), dis[i]=m-d[i].y;
	dis[k+1]=m; q.push((node){m, k+1});
	while(!q.empty()){
		int u=q.top().id; q.pop();
		if(vis[u]) continue;
		ans=max(ans, dis[u]); vis[u]=1;
		if(u==k+1) return;
		for(int i=1; i<=k; ++i){
			if(vis[i]) continue;
			double len=dist(i, u);
			if(len<dis[i])
				dis[i]=len, q.push((node){dis[i], i});
		}
		if(d[u].y<dis[k+1]) dis[k+1]=d[u].y, q.push((node){dis[k+1], k+1});
	}
}
int main(void){
	n=read(), m=read(), k=read();
	int a, b;
	for(int i=1; i<=k; ++i){
		a=read(), b=read();
		d[i]=make_pair(a, b);
	}
	prim();
	printf("%.8lf\n", ans/2);
	return 0;
}

B. God Knows

考虑逆序对,然而这题和逆序对并没有关系。。。。
要从一段区间内选出一个子序列,考虑 \(dp\)

考虑 \(dp\) ,设 \(f_{i}\) 表示选到第 \(i\) 个且选了 \(i\) 的最小价值。

思考 \(f_{i}\) 可以由那些点更新过来。
显然,若选的上一个点是 \(j\) ,则 \(j\) 显然不能与 \(i\) 相切,即 \(p_{j} < p_{i}\) ,且 \(i,j\) 中的每一个点都被这两个点所切,即 \(\forall k \in [i,j],p_{k}<p_{j}||p_{k}>p{i}\)
若将每个点想成点对 \((i, p_{i})\) ,则满足上述条件的两个点,在坐标中能以 \(i\) 点为右上角, \(j\) 点为左下角,构成一个矩形,且矩形中没有其他点。
考虑用线段树维护,若将 \(i\) 作为下标,每次把 \(p_{i}\) 存到线段树中,会发现每次查询时,有许多点的值大于当前值,这样会查询会很麻烦,考虑将每个点变为 \((p_{i},i)\) ,以 \(p_{i}\) 为下标,将 \(i\) 加入到线段树中,这样由于是从 \(1\)\(n\) 依次加入,新加入的数肯定比之前的大。

考虑线段树上的一个区间,若要找这个区间的答案最小值,显然这个区间的右子区间会对左子区间的答案有影响,若左子区间内的点,若其值小于右子区间的最大值,则显然对答案无贡献,所以要先求得右子区间的最小答案与最大值,再在左区间内寻找大于左子区间的值。
但是这样时间复杂度 \(O(n^{2}\log n)\) 还不如直接暴力快,于是考虑优化。
显然对于一个区间,其右子区间的最大值是一个定值,所以可以直接预处理出其左子区间的答案的最小值,每次新插入一个数进行修改,每次询问直接返回即可。
这样每次修改 \(O(\log^{2}n)\) ,每次询问 \(O(\log n)\) ,总时间复杂度 \(O(n\log^{2}n)\) ,显然可过。

code
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10, INF=0x7fffffff;
inline int read(){
	int f=1, x=0; char ch=getchar();
	while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
	while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
	return f*x;
}
int n;
int stk[N], top;
int p[N], c[N], f[N];
struct TRE{
	int l, r;
	int minn, maxn;
}t[N<<2];
void built(int l, int r, int p){
	t[p].l=l, t[p].r=r; t[p].maxn=-INF;
	if(l==r) return;
	int mid=(l+r)>>1;
	built(l, mid, p<<1); built(mid+1, r, p<<1|1);
}
int maxn(int l, int r, int p){
	if(l<=t[p].l&&r>=t[p].r) return max(t[p].maxn, 0);
	int mid=(t[p].l+t[p].r)>>1;
	if(l<=mid&&r>mid) return max(max(maxn(l, r, p<<1), maxn(l, r, p<<1|1)), 0);
	if(l<=mid) return max(maxn(l, r, p<<1), 0);
	return max(maxn(l, r, p<<1|1), 0);
}
int ask(int x, int p){
	if(x>t[p].maxn) return INF;
	if(t[p].l==t[p].r) return f[t[p].maxn];
	if(x>t[p<<1|1].maxn) return ask(x, p<<1);
	else return min(t[p].minn, ask(x, p<<1|1));
}
void upd(int p){
	t[p].maxn=max(t[p<<1].maxn, t[p<<1|1].maxn);
	if(t[p<<1|1].maxn==-INF) t[p].minn=ask(0, p<<1);
	else t[p].minn=ask(t[p<<1|1].maxn, p<<1);
}
int minn(int x, int p){
	if(t[p].l>x) return INF;
	if(t[p].r<x) return ask(maxn(t[p].r+1, x, 1), p);
	if(t[p].r==x) return ask(0, p);
	int mid=(t[p].l+t[p].r)>>1;
	if(x>mid) return min(minn(x, p<<1), minn(x, p<<1|1));
	return minn(x, p<<1);
}
void change(int x, int w, int p){
	if(t[p].l==t[p].r) { t[p].maxn=w; return; }
	int mid=(t[p].l+t[p].r)>>1;
	if(x<=mid) change(x, w, p<<1);
	else change(x, w, p<<1|1);
	upd(p);
}
int main(void){
	n=read();
	for(int i=1; i<=n; ++i) p[i]=read();
	for(int i=1; i<=n; ++i) c[i]=read();
	built(0, n, 1); change(0, 0, 1);
	for(int i=1; i<=n; ++i){
		f[i]=minn(p[i], 1)+c[i];
		change(p[i], i, 1);
	}
	printf("%d\n", minn(n, 1));
	return 0;
}

C. Lost My Music

乍一看像一个斜率优化,然而我并不会斜率优化。。。。
某巨佬:这其实不能算作是不是斜率优化,维护个凸包就好了。

把每个节点看成 \((c_{u},dep_{u})\) ,那么题中的式子实际上就是一个两点间斜率的定义式,这样维护一个下凸包即可,每次新加入一个点倍增修改即可。
时间复杂的 \(O(n\log n)\) ,可过。

code
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+10;
inline int read(){
	int f=1, x=0; char ch=getchar();
	while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
	while(isdigit(ch)) { x=x*10+ch-48; ch=getchar(); }
	return f*x;
}
int n, c[N], dis[N];
vector<int> l[N];
int fa[N][30];
double k(int u, int v) { return double(c[u]-c[v])/(dis[u]-dis[v]); }
void dfs(int u, int f){
	dis[u]=dis[f]+1;
	for(int i=20; ~i; --i){
		if(fa[f][i]<=1) continue;
		if(k(fa[fa[f][i]][0], fa[f][i])>=k(fa[f][i], u)) f=fa[f][i];
	}
	if(f!=1&&k(fa[f][0], f)>=k(f, u)) f=fa[f][0];
	fa[u][0]=f;
	for(int i=1; i<=20; ++i) fa[u][i]=fa[fa[u][i-1]][i-1];
	for(int v : l[u]) dfs(v, u);
}
int main(void){
	n=read(); int x;
	for(int i=1; i<=n; ++i) c[i]=read();
	for(int i=2; i<=n; ++i){
		x=read();
		l[x].push_back(i);
	}
	dfs(1, 0);
	for(int i=2; i<=n; ++i) printf("%.10lf\n", (double)(c[fa[i][0]]-c[i])/(dis[i]-dis[fa[i][0]]));
	return 0;
}
posted @ 2021-07-16 16:04  Cyber_Tree  阅读(54)  评论(2编辑  收藏  举报