恩欧挨批模拟试题-21

水博客太快乐了

RT

考场

发现这场考过。。。终于有一场我考过的了。。。

于是大概看了一下题就跑去复习之前的考试了。。。

\(T1\) 真是异常毒瘤,那个时间复杂度异常玄学。。。完全不敢让人相信是正解。。。
\(T2\) 是个大水题。。。然而当年我考的时候并没有做出来。。。
\(T3\) 是肉眼可见的树形 \(dp\)

分数

因为没考所以没有。。。

题解

A. Median

因为测评姬太菜,卡了std,所以时间从3s加到了4s。。。

这题一看就不想是考场该做的题。。。考场应该直接暴力走人。。。。
然而巨佬 \(HKHbest\) 还是场切了这道题。。。让我们一起膜拜他。。。

实际上这题的思想异常的简单。。。
就是先开个桶把前 \(k\) 个数存下来并确定其中位数,每次对数列进行更改的时候,在桶中操作,将原中位数左移或右移以确定新的中位数。。。
乍看下时间复杂度完全错误,然而这题的数据较为随机且很难构造,所以每次移动都不会移太远。。。。。

虽然思想很简单,代码中还是有很多细节要注意的。。。
我第一次写就挂了。。。

code
#include<bits/stdc++.h>
using namespace std;
const int N=1e7+10, NN=179424674;
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, k, w;
int s1[N], s2[N], t[N], pri[N], cnt;
bool vis[NN];
double ans;
inline void pre(){
	for(int i=2; i<NN; ++i){
		if(!vis[i]) pri[++cnt]=i, s1[cnt]=1ll*i*cnt%w, s2[cnt]=s1[cnt]+s1[cnt/10+1];
		for(int j=1; j<=cnt&&pri[j]*i<NN; ++j){
			vis[i*pri[j]]=1;
			if(i%pri[j]==0) break;
		}
	}
}
int main(void){
	n=read(), k=read(), w=read(); pre();
	for(int i=1; i<k; ++i) t[s2[i]]++;
	if(k&1){
		int mid=(k>>1)+1, lft=0, m=-1;
		for(int i=k; i<=n; ++i){
			t[s2[i]]++;
			if(s2[i]<=m) lft++;
			if(i!=k){
				t[s2[i-k]]--;
				if(s2[i-k]<=m) lft--;
			}
			while(lft<mid) lft+=t[++m];
			while(lft>=mid+t[m]) lft-=t[m--];
			ans+=m;
		}
	}else{
		int mid=(k>>1), l1=0, l2=0, ml=-1, mr=-1;
		for(int i=k; i<=n; ++i){
			t[s2[i]]++;
			if(s2[i]<=ml) l1++;
			if(s2[i]<=mr) l2++;
			if(i!=k){
				t[s2[i-k]]--;
				if(s2[i-k]<=ml) l1--;
				if(s2[i-k]<=mr) l2--;
			}
			while(l1<mid) l1+=t[++ml];
			while(l2<mid+1) l2+=t[++mr];
			while(l1>=mid+t[ml]) l1-=t[ml--];
			while(l2>=mid+t[mr]+1) l2-=t[mr--];
			ans+=(double)(ml+mr)/2;
		}
	}
	printf("%.1lf\n", ans);
	return 0;
}

B. Game

注意到 \(1 \le a_{i} \le n\) 必然要考虑桶排。。。

对于每次操作,都把前 \(p_{i}\) 个数加入到桶里,然后扫一遍桶,每次取最大的,每取一个数会新加入一个数,若加入的数比当前扫到的值大,就直接算进答案的贡献里,若小则加入桶中。
这题要大力卡常。。。

code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+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, num, k, p, a[N], vis[N];
long long ans;
bool j;
int main(void){
	n=read(), k=read();
	for(int i=1; i<=n; ++i) a[i]=read();
	while(k--){
		p=read(); ans=0; j=1; num=n;
		for(int i=1; i<=p; ++i) vis[a[i]]++;
		for(int i=n; num&&i; --i)
			while(vis[i]){
				ans+=j ? i : -i, j^=1; --vis[i]; --num;
				if(p>n) continue;
				while(a[++p]>=i&&p<=n) { ans+=j ? a[p] : -a[p]; j^=1; --num; }
				if(p<=n) vis[a[p]]++;
			}
		printf("%lld\n", ans);
	}
	return 0;
}

C. Park

这题和 [CEOI2017]Chase 是重题。。。(\(CEOI\)\(Central \ European \ Olympiad \ in \ Informatics\) 而不是 \(Compile \ Error \ Olympiad \ in \ Informatics\) 。。。

乍一看就知道应该是一道树形 \(dp\)

思考应该如何设计状态。。。
首先观察问题的本质。。。
\(XXS\) 比旅行家多遇到的鸽子来自哪些节点,显然是他撒面包屑的哪些节点周围所有点的点权和,然而加入旅行者从 \(u\) 节点走到 \(v\) 节点,那么他显然已经遇到了 \(u\) 节点的鸽子,而 \(XXS\) 也会遇到 \(u\) 节点的鸽子,那么这个节点的权值显然不应该算入答案中。。。
由此不难看出,题中的答案是与方向有关的。也就是说,从一个节点走到它子树中的最大答案,与从它子树中的走到这个节点的最大答案是不同的。。。
因此在设计状态时要着重注意方向,不妨设 \(f_{u,i}\) 表示从子树中走到节点 \(u\) 共在 \(i\) 个点撒了面包屑的答案,相对的, \(g_{u,i}\) 表示从 \(u\) 节点走到它的子树中,共在 \(i\) 个节点撒了面包屑的答案。

设计出状态的话,转移便是显然的:
\(f_{u,i}=max(f_{u,i},f_{v,i},f_{v,i-1}+sum_{u}-a_{v})\)
\(g_{u,i}=max(g_{u,i},g_{v,i},g_{v,i-1}+sum_{u}-a_{fa})\)
其中 \(sum_{u}\) 表示 \(u\) 节点周围所有节点的权值和,而 \(a_{u}\) 表示 \(u\) 节点的权值。
接下来思考如何更新答案,答案显然是从当前点的某一棵子树走到另一棵子树中,即:
\(ans=max(ans, f_{u,i}+g_{v,m-i})\)
\(m\) 表示最多选择 \(m\) 个撒面包, \(f_{u,i}\) 表示当前点已经计算过的子树中某一个点走到当前点的答案,而 \(g_{v,m-i}\) 表示从 \(v\) 节点走到还没有计算过的一棵子树中,这样可以避免两条路径出自同一棵子树。
然而这样还有一个问题,因为路径是有方向的,而若按某一特定顺序扫过所有子树,则势必会少计算一些路径,因此要把所有子树反过来再扫一遍,重新统计答案。。

code
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+10, V=110;
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, ans;
int a[N], sum[N], f[N][V], g[N][V];
vector<int> l[N];
void dfs(int u, int fa){
	for(int i=1; i<=m; ++i) f[u][i]=sum[u], g[u][i]=sum[u]-a[fa];
	for(int v : l[u]){
		if(v==fa) continue;
		dfs(v, u);
		for(int i=0; i<=m; ++i) ans=max(ans, f[u][i]+g[v][m-i]);
		for(int i=1; i<=m; ++i){
			f[u][i]=max(f[u][i], max(f[v][i], f[v][i-1]+sum[u]-a[v]));
			g[u][i]=max(g[u][i], max(g[v][i], g[v][i-1]+sum[u]-a[fa]));
		}
	}
	for(int i=1; i<=m; ++i) f[u][i]=sum[u], g[u][i]=sum[u]-a[fa];
	for(int i=l[u].size()-1; ~i; --i){
		int v=l[u][i];
		if(v==fa) continue;
		for(int i=0; i<=m; ++i) ans=max(ans, f[u][i]+g[v][m-i]);
		for(int i=1; i<=m; ++i){
			f[u][i]=max(f[u][i], max(f[v][i], f[v][i-1]+sum[u]-a[v]));
			g[u][i]=max(g[u][i], max(g[v][i], g[v][i-1]+sum[u]-a[fa]));
		}
	}
}
signed main(void){
	n=read(), m=read();
	int x, y;
	for(int i=1; i<=n; ++i) a[i]=read();
	for(int i=1; i<n; ++i){
		x=read(), y=read();
		l[x].push_back(y); sum[x]+=a[y];
		l[y].push_back(x); sum[y]+=a[x];
	}
	dfs(1, 0); printf("%lld\n", ans);
	return 0; 
}

发现突然多了好多粉丝!??
受宠若惊并一一回关了。。。

posted @ 2021-07-21 09:17  Cyber_Tree  阅读(49)  评论(0编辑  收藏  举报