恩欧挨批模拟试题-21
水博客太快乐了
考场
发现这场考过。。。终于有一场我考过的了。。。
于是大概看了一下题就跑去复习之前的考试了。。。
\(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;
}
发现突然多了好多粉丝!??
受宠若惊并一一回关了。。。