2025/2/18 模拟赛 总结

好久没有这么顺利的AK一场模拟赛了,写完还剩 1h,给足了我写 Solution 的时间

T1:

农夫约的假期 (shuru.pas/c/cpp)

Description

【问题描述】

在某国有一个叫农夫约的人,他养了很多羊,其中有两头名叫mm和hh,他们的歌声十分好听,被当地人称为“魔音”······

农夫约也有自己的假期呀!他要去海边度假,然而mm和hh不能离开他。没办法,他只好把他们两个带上。

到了海边,农夫约把他的羊放在一个(nn)的矩阵(有nn个方格)里。mm和hh十分好动,他们要走到m(m<=n*n)个地方,第i个地方的坐标为(x[i](行),y[i](列)),每到一个地方他们会高歌一曲,制造q[i]点魔音值,因为他们的魔音十分独特,他们的声音只能横着或竖着传播。每传播一格,魔音值会增加1。(传播的格子数取最小的)接下来农夫约要住酒店。为了方便照顾小羊们,他选的酒店的坐标要在矩阵内。但小羊们的魔音让他十分头疼。他想求出魔音值最小的地方。

他还要享受他的假期,所以他把这个任务交给你了,加油(_)。

数据范围

100%的数据,0<n<=100000,0<m<=100000,0<z<=10,0<q[i]<=100.

Solution:

问题转化:

不难想到,我们可以把这个问题转化成在这个平面上选一个点,使得剩下所有关键点到它的 曼哈顿距离 之和最小。然后曼哈顿距离又可以拆成横纵坐标来方便维护,所以我们将这个平面拍到两个数轴上,我们在横纵坐标上方便选择各自的中位数,但是由于我们不太清楚在哪个点可以取到最小化的横纵坐标,所以主播直接在跑出各自中位数后在一个比较小的正方形内再查询一下。

实现:

我们开两颗线段树来维护点的坐标,这样我们就可以做到 O(logn) 单点查询一个点处的魔音值了。

Code:

#include<bits/stdc++.h>
#define ll long long
#define int long long
const int N=1e5+5;
const ll inf=1e17;
using namespace std;
struct Segment_Tree{
struct Tree{
int cnt;ll sum;
}t[N<<2];
#define ls x<<1
#define rs x<<1|1
void insert(int x,int l,int r,int pos)
{
t[x].cnt++,t[x].sum+=pos;
if(l==r)return;int mid=l+r>>1;
if(pos<=mid)insert(ls,l,mid,pos);
else insert(rs,mid+1,r,pos);
}
int query_cnt(int x,int l,int r,int L,int R)
{
if(R<L)return 0;
if(L<=l&&r<=R)return t[x].cnt;
int mid=l+r>>1,res=0;
if(L<=mid)res+=query_cnt(ls,l,mid,L,R);
if(mid<R)res+=query_cnt(rs,mid+1,r,L,R);
return res;
}
ll query_sum(int x,int l,int r,int L,int R)
{
if(R<L)return 0;
if(L<=l&&r<=R)return t[x].sum;
int mid=l+r>>1;ll res=0;
if(L<=mid)res+=query_sum(ls,l,mid,L,R);
if(mid<R)res+=query_sum(rs,mid+1,r,L,R);
return res;
}
}Tx,Ty;
struct task{
int x,y;
bool operator <(const task &t)const{
return x==t.x ? y<t.y : x<t.x;
}
}q[N];
int n,m,z;
ll ans[3],val;
ll calc(int x,int y)
{
int sum=0,cnt=0,tmp=0;
// x:
cnt=Tx.query_cnt(1,1,n,1,x);tmp=Tx.query_sum(1,1,n,1,x);//[1,x]
sum+=-tmp+1ll*x*cnt;
cnt=Tx.query_cnt(1,1,n,x+1,n);tmp=Tx.query_sum(1,1,n,x+1,n);//(x,n]
sum+=-1ll*x*cnt+tmp;
//y:
cnt=Ty.query_cnt(1,1,n,1,y);tmp=Ty.query_sum(1,1,n,1,y);
sum+=-tmp+1ll*y*cnt;
cnt=Ty.query_cnt(1,1,n,y+1,n);tmp=Ty.query_sum(1,1,n,y+1,n);
sum+=-1ll*y*cnt+tmp;
return sum;
}
int a[N],b[N];
void work()
{
cin>>n>>m>>z;
ans[0]=inf;
ll xx=0,yy=0;
for(int i=1,w;i<=m;i++)
{
scanf("%lld%lld%lld",&q[i].x,&q[i].y,&w);val+=w;
Tx.insert(1,1,n,q[i].x);
Ty.insert(1,1,n,q[i].y);
a[i]=q[i].x,b[i]=q[i].y;
}
sort(a+1,a+1+m);sort(b+1,b+1+m);
xx=a[m>>1],yy=b[m>>1];
for(int i=-500;i<=500;i++)for(int j=-500;j<=500;j++)
if(1<=xx+i&&xx+i<=n&&1<=yy+j&&yy+j<=n)
{
ll tmp=calc(xx+i,yy+j);
if(ans[0]>tmp)
{
ans[0]=tmp;ans[1]=xx+i,ans[2]=yy+j;
}
}
ans[0]=1ll*ans[0];
printf("%lld\n%lld %lld",ans[0]+val,ans[1],ans[2]);
}
#undef int
int main()
{
freopen("shuru.in","r",stdin);freopen("shuru.out","w",stdout);
work();
return 0;
}

T2:

小x游世界树 (yggdrasi.pas/c/cpp)
Description

【问题描述】

小x得到了一个(不可靠的)小道消息,传说中的神岛阿瓦隆在格陵兰海的某处,据说那里埋藏着亚瑟王的宝藏,这引起了小x的好奇,但当他想前往阿瓦隆时发现那里只有圣诞节时才能到达,然而现在已经春天了,不甘心的他将自己的目的地改成了世界树,他耗费了大量的时间,终于将自己传送到了世界树下。世界树是一棵非常巨大的树,它有着许许多多的枝条以及节点,每个节点上都有一个平台。好不容易来到传说中的世界树下,小x当然要爬上去看看风景。小x每经过一条边都会耗费体力值。然而世界树之主想给他弄(gáo)些(dǐan)麻(shì)烦(qíng),于是他在每条边上都设了一个魔法阵,当小x踏上那条边时会被传送回根节点,魔法阵只生效一次。这岂不是要累死小x?幸运的是,每个平台上都有无数个加速器,这些加速器可以让小x在当前节点所连的边上耗费的体力值减少,不同平台的加速器性能不一定相同,但同一个平台的加速器性能绝对相同。世界树之主给了小x一次“换根”的机会,他可以将世界树的任何一个节点变为根,但所有的边都不能改变。小x想问你,将根换为哪个节点能使小x爬到世界树上的每个节点耗费的体力值和最少。默认编号为1的点为初始根。

数据范围

对于100%的数据:0<n<=700000;ai<=1000;1<=bi,ci<=n;di<=1000。

Solution:

我们思考如何刻画一条边对于其两个端点的影响:

下文我们将以1为根时的边的方向来区分正反向,节点的 dfn 也是以1为根时定义的。

首先我们不难想到节点上的加速器就是让我们将原本无向的一条边拆成两条有向的,权值分别为 wau,wav 的两条正反向的边。
一条边如果在最终的正向的,那么他产生的贡献就是其边权 w 乘上其指向的子树大小 sizv
这样做的意义就是假如最终选出来的根在这条边的起点 u 及其上方,那么这条边就会产生 w×sizv 的贡献。

我们考虑将这个贡献放到线段树上进行维护,区间修改单点查询。然后在算完所有边之后对线段树的每一个叶子节点进行查询,权值最小的叶子节点就是答案。

Code:

#include<bits/stdc++.h>
#define int long long
const int N=7e5+5;
const int inf=1e17;
using namespace std;
int n,cnt;
int a[N],val[N],fa[N],siz[N],st[N],ed[N],rid[N];
int tans[N];
struct Edge{
int to,w,nxt;
}e[N<<1];int head[N];
void add(int x,int y,int w)
{
e[++head[0]]={y,w,head[x]};head[x]=head[0];
}
struct task{
int u,v,w;
}q[N<<1];
void dfs(int x,int f)
{
fa[x]=f;siz[x]=1;rid[st[x]=++cnt]=x;
for(int i=head[x],y,w;i;i=e[i].nxt)
{
y=e[i].to,w=e[i].w;
if(y==fa[x])continue;
dfs(y,x);val[y]=w;
siz[x]+=siz[y];
}ed[x]=cnt;
}
struct Segment_Tree{
struct Tree{
int sum,tag;
}t[N<<2];
#define ls x<<1
#define rs x<<1|1
void add(int x,int k){t[x].sum+=k,t[x].tag+=k;}
void pushdown(int x)
{
if(!t[x].tag)return ;
add(ls,t[x].tag);add(rs,t[x].tag);
t[x].tag=0;
}
void upd(int x,int l,int r,int L,int R,int k)
{
if(L>R)return;
if(L<=l&&r<=R){add(x,k);return;}
int mid=l+r>>1;pushdown(x);
if(L<=mid)upd(ls,l,mid,L,R,k);
if(mid<R)upd(rs,mid+1,r,L,R,k);
}
int query(int x,int l,int r,int pos)
{
if(l==r)return t[x].sum;
int mid=l+r>>1;pushdown(x);
if(pos<=mid)return query(ls,l,mid,pos);
return query(rs,mid+1,r,pos);
}
}T;
int ans[2];
void work()
{
cin>>n;
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
for(int i=2,x,y,w;i<=n;i++)
{
scanf("%lld%lld%lld",&x,&y,&w);
add(x,y,w);add(y,x,w);
}
dfs(1,0);
for(int u=2,v;u<=n;u++)
{
v=fa[u];
T.upd(1,1,n,st[u],ed[u],(val[u]-a[u])*(n-siz[u]));
T.upd(1,1,n,1,st[u]-1,(val[u]-a[v])*siz[u]);
T.upd(1,1,n,ed[u]+1,n,(val[u]-a[v])*siz[u]);
}
ans[0]=inf;
for(int u=n;u;u--)
{
int tmp=T.query(1,1,n,u);
if(ans[0]>tmp){ans[0]=tmp,ans[1]=u;}
}
printf("%lld\n%lld",rid[ans[1]],ans[0]);
}
#undef int
int main()
{
freopen("yggdrasi.in","r",stdin);freopen("yggdrasi.out","w",stdout);
work();
return 0;
}

T3:

观察 (watch.pas/c/cpp)
Description

【问题描述】

infleaking十分愉快地走在路上,

因为经过1099^9年后,

他得到了一个新技能——观察大法。

刚出来的infleaking就想要挑战自我。

为什么infleaking会这么自信呢?

因为infleaking做到了可以通过观察数据就就可以得出答案。

但是出题人十分不服,想要将infleaking的气焰打压下去,

于是想到了一道题。

结果被infleaking运用他那强大的观察能力看完数据后给出了答案。

怎么能够让infleaking继续下去呢,出题人于是就将数据重出并且加密了。

没有能直接观察数据的infleaking十分不服气,想要解决这道题,

但是苦于不能直接使用他的新技能,所以想要请聪明的你帮infleaking解决这个问题。

出题人给出一颗以1为根的树,一开始每个节点都是一颗棋子,一面白一面黑,白色的面朝上

接下来就q次操作,操作分两种

0操作将一个颗棋子翻转

1操作询问一颗棋子与所有面朝上为黑色的棋子lca最深的那个的编号

Solution:

问题转化:

首先我们不难想到向上跳祖先来检查这个祖先是否满足题意,那么如何检查呢?我们可以将这颗树拍到 dfs 序上,然后在 dfn 序列上维护当前区间 [l,r] 内有多少个黑点。假设当前询问点是 x 那么 check x 的一个祖先 pos 的方式就是看 [stposstx) (edx,edpos] 上是否有黑点。

实现:

我们发现这个 pos 是满足单调性的,所以我们可以倍增向上跳,然后用常数比较小的树状数组来维护黑点个数的前缀和,这样虽然是 O(nlog2n) 的,但是由于常数比较好,我们仍然可以通过此题。

Code:

#include<bits/stdc++.h>
const int N=1.6e6+5;
const int lg=20;
using namespace std;
int n,m,tot;
int sum[N];
inline int lowbit(int x){return x&-x;}
inline void upd(int x,int k)
{
for(int i=x;i<=n;i+=lowbit(i))sum[i]+=k;
}
inline int calc(int x)
{
int res=0;for(int i=x;i>0;i-=lowbit(i))res+=sum[i];return res;
}
inline int query(int l,int r)
{
return calc(r)-calc(l-1);
}
struct Edge{
int to,nxt;
}e[N];int head[N];
void add(int x,int y){e[++head[0]]={y,head[x]};head[x]=head[0];}
int f[N][lg+5],dep[N],st[N],ed[N];
void dfs(int x,int fa)
{
dep[x]=dep[f[x][0]=fa]+1;st[x]=++tot;
for(int j=1;j<=lg;j++)f[x][j]=f[f[x][j-1]][j-1];
for(int i=head[x],y=e[i].to;i;y=e[i=e[i].nxt].to)dfs(y,x);ed[x]=tot;
}
bool check(int x,int y)
{
return query(st[x],ed[x])-query(st[y],ed[y]);
}
bool use[N];
int solve(int x)
{
if(use[x]||query(st[x],ed[x]))return x;
for(int j=lg;j>=0;j--)if(f[x][j]&&!check(f[x][j],x))x=f[x][j];
return f[x][0];
}
void work()
{
cin>>n>>m;
for(int v=2,u;v<=n;v++)
{
scanf("%d",&u);add(u,v);
}
dfs(1,0);
for(int i=1,x;i<=m;i++)
{
scanf("%d",&x);use[x]^=1;
if(x>0)upd(st[x],use[x] ? 1 : -1);
else printf("%d\n",solve(-x));
}
}
int main()
{
freopen("watch.in","r",stdin);freopen("watch.out","w",stdout);
work();
return 0;
}
posted @   liuboom  阅读(2)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示