当前位置: 首页 > java >正文

GJOI 5.15 题解

1.喷泉

题意

一排喷泉有 n n n 个喷头,初始时全部处于“开启”状态。每次操作可以改变连续 k k k 个喷头的状态(开启变关闭,关闭变开启)。经过若干次操作后,最多能形成多少种不同的喷泉状态?由于答案可能很大,请对 1 0 9 + 7 10^9+7 109+7 取模。

1 ≤ k ≤ n ≤ 1 0 5 1\le k\le n\le 10^5 1kn105

思路

打表即可。不难发现答案为 2 n − k + 1 2^{n-k+1} 2nk+1。至于证明,我们可以分别考虑 ( n − 1 , k ) (n-1,k) (n1,k) ( n , k − 1 ) (n,k-1) (n,k1) 如何转移到 ( n , k ) (n,k) (n,k),从略。

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=1e5+9,mod=1e9+7;
ll n,k;
ll qpow(ll x,ll k)
{ll ret=1;while(k){if(k&1)ret=ret*x%mod;x=x*x%mod;k>>=1;}return ret;
}
int main()
{scanf("%lld%lld",&n,&k);printf("%lld",qpow(2,n-k+1));return 0;
}

2.AT_abc298_f Rook Score

题意

在这里插入图片描述
1 ≤ n ≤ 2 × 1 0 5 1\le n\le 2\times 10^5 1n2×105 1 ≤ r , c , v ≤ 1 0 9 1\le r,c,v\le 10^9 1r,c,v109

思路

先离散化。设 h a n g i hang_i hangi l i e i lie_i liei 分别表示离散化后每一行每一列的数值总和, x . x x.x x.x x . y x.y x.y 分别表示离散化后点 x x x 的行和列, x . v x.v x.v 表示 x x x 的数值。

我们可以枚举每一行,然后我们查询每一列出去当前行的交叉点值的最大值。我们存下每一行每一个有数值的点,然后枚举第 i i i 行——怎么修改数据呢?

其实我们可以把所有 l i e i lie_i liei 扔上平衡树,在枚举 i i i 行上的(有数值的)点编号 x x x 时,暴力删除 l i e x . y lie_{x.y} liex.y,然后扔 l i e x . y − x . v lie_{x.y}-x.v liex.yx.v 上去。查询最大的列贡献加上 h a n g i hang_i hangi,对所有贡献取最大值即可。记得复原更新的数据。

枚举 x x x 均摊复杂度来到 Θ ( n ) \Theta(n) Θ(n),平衡树修改和查询就 Θ ( log ⁡ n ) \Theta(\log n) Θ(logn),离散化用了map(不过快排更快)。用 FHQ-Treap 实现平衡树在 2 × 1 0 5 2\times 10^5 2×105 的极限数据下跑了 2.2s \text{2.2s} 2.2s

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=1e6+9;
ll n;
ll rt,cnt;
struct Node
{ll ls,rs;ll val,pri;//键值 随机优先级 ll siz;//子树大小 
}T[N];
void newNode(ll x)
{cnt++;T[cnt].siz=1;T[cnt].ls=T[cnt].rs=0;T[cnt].val=x;T[cnt].pri=rand();
}
void pushup(ll u)
{T[u].siz=T[T[u].ls].siz+T[T[u].rs].siz+1;
}
void split(ll u,ll k,ll &L,ll &R)//分裂操作 
{if(u==0){L=R=0;return;}if(T[u].val<=k){L=u;split(T[u].rs,k,T[u].rs,R);}else {R=u;split(T[u].ls,k,L,T[u].ls);}pushup(u);
}
ll merge(ll L,ll R)//合并两棵根为L和R的树,返回树根 
{if(!L||!R)return L|R;if(T[L].pri>T[R].pri){T[L].rs=merge(T[L].rs,R);pushup(L);return L;}else {T[R].ls=merge(L,T[R].ls);pushup(R);return R;}
}
void insert(ll x)//插入一个数x 
{ll L,R;split(rt,x,L,R);newNode(x);rt=merge(merge(L,cnt),R);
}
void del(ll x)//删除一个数x
{ll L,R,u;split(rt,x,L,R);//<=x的树和>x的树 split(L,x-1,L,u);//<x的树和=x的树 u=merge(T[u].ls,T[u].rs);//合并<x和>x的树,就删除了x rt=merge(merge(L,u),R);
}
ll kth(ll u,ll k)//排名为k的节点编号 
{if(k==T[T[u].ls].siz+1)return u;if(k<=T[T[u].ls].siz)return kth(T[u].ls,k);//目标在左return kth(T[u].rs,k-T[T[u].ls].siz-1);//目标在右
}
struct point
{ll x,y,v;
}a[N];
ll cx,cy;
ll hang[N],lie[N];
vector<ll>vec[N]; 
map<ll,ll>mpx,mpy;
int main()
{srand(time(0));scanf("%lld",&n);for(int i=1;i<=n;i++){ll x,y,v;scanf("%lld%lld%lld",&x,&y,&v);if(!mpx[x])mpx[x]=++cx;if(!mpy[y])mpy[y]=++cy;a[i]=(point){mpx[x],mpy[y],v};}ll ans=0;for(int i=1;i<=n;i++){hang[a[i].x]+=a[i].v;lie[a[i].y]+=a[i].v;vec[a[i].x].push_back(i);}for(int i=1;i<=cx;i++)ans=max(ans,hang[i]);for(int i=1;i<=cy;i++)ans=max(ans,lie[i]);for(int i=1;i<=cy;i++)insert(lie[i]);for(int i=1;i<=cx;i++){for(auto x:vec[i]){del(lie[a[x].y]);insert(lie[a[x].y]-a[x].v);}ans=max(ans,T[kth(rt,cy)].val+hang[i]);for(auto x:vec[i]){del(lie[a[x].y]-a[x].v);insert(lie[a[x].y]);}}printf("%lld",ans);return 0;
}

3.AT_abc298_h Sum of Min of Length

题意

在这里插入图片描述
1 ≤ n , m ≤ 2 × 1 0 5 1\le n,m\le 2\times 10^5 1n,m2×105 1 ≤ l , r ≤ n 1\le l,r\le n 1l,rn

思路

有个 min ⁡ \min min 很麻烦,如果直接做就要枚举 Θ ( n 2 log ⁡ n ) \Theta(n^2\log n) Θ(n2logn),所以我们考虑对 min ⁡ \min min 做处理,我们考虑哪些点离 L L L 更近,离 R R R 更近。

我们钦定 L L L 更浅 R R R 更深,找一个路径中点 M i d Mid Mid,我们就可以知道哪些点必然离 L L L 近,必然离 R R R 近。举个例子更直观:
在这里插入图片描述

我们钦定 R R R 更深,那么 M i d Mid Mid 肯定会在 lca ( L , R ) \text{lca}(L,R) lca(L,R) R R R 的链上。(待会再讨论刚好在 lca ( L , R ) \text{lca}(L,R) lca(L,R) 的情况)

我们规定几个数组: s i z u siz_u sizu 表示子树 u u u 的大小, s u m u sum_u sumu 表示以 1 1 1 为根时 u u u 到子树内每个点的距离总和, s d u sd_u sdu 表示 u u u 为整棵树的根时到每个节点的距离总和, dis ( u , v ) \text{dis}(u,v) dis(u,v) 表示树上 u u u v v v 的距离。这些都可以 dfs 和倍增求 lca 得到。

对于离 L L L 更近的蓝色部分,我们用 s d L sd_L sdL 减去 M i d Mid Mid 子树内所有点到 L L L 的距离: M i d Mid Mid 子树内的所有点先全部走到 M i d Mid Mid s u m M i d sum_{Mid} sumMid),然后 s i z M i d siz_{Mid} sizMid 个点一起从 M i d Mid Mid 走到 L L L s i z M i d × dis ( L , M i d ) siz_{Mid}\times \text{dis}(L,Mid) sizMid×dis(L,Mid))。
a n s L = s d L − ( s u m M i d + dis ( L , M i d ) × s i z M i d ) ans_L=sd_L-(sum_{Mid}+\text{dis}(L,Mid)\times siz_{Mid}) ansL=sdL(sumMid+dis(L,Mid)×sizMid)

对于离 R R R 更近的红色部分,我们用 s d R sd_R sdR 减去 M i d Mid Mid 子树之外的所有点到 R R R 的距离,两个部分反过来:
a n s R = s d R − [ ( s d M i d − s u m M i d ) + dis ( R , M i d ) × ( n − s i z M i d ) ] ans_R=sd_R-[(sd_{Mid}-sum_{Mid})+\text{dis}(R,Mid)\times (n-siz_{Mid})] ansR=sdR[(sdMidsumMid)+dis(R,Mid)×(nsizMid)]

我们要从 R R R 跳到 M i d Mid Mid,理应是路径的中点 dis ( L , R ) 2 \dfrac{\text{dis}(L,R)}{2} 2dis(L,R),但是如果 M i d Mid Mid 刚好在 lca ( L , R ) \text{lca}(L,R) lca(L,R) 上,根据上面推的式子, s d M i d − s u m M i d sd_{Mid}-sum_{Mid} sdMidsumMid 直接归零,并不包含我们想要的 M i d Mid Mid L L L 的路径和。因此我们考虑靠近 R R R 推一位,我们取 M i d = ⌊ dis ( L , R ) − 1 2 ⌋ Mid=\left \lfloor \dfrac{\text{dis}(L,R)-1}{2} \right \rfloor Mid=2dis(L,R)1

代码

#include<bits/stdc++.h>
#pragma GCC optimise(2)
#pragma GCC optimise(3,"Ofast","inline")
using namespace std;
#define ll long long
const ll N=1e5+9,mod=1e9+7;
ll n,m;
ll idx,head[N];
struct edge
{ll to,next;
}e[N<<1];
void addedge(ll u,ll v)
{idx++;e[idx].to=v;e[idx].next=head[u];head[u]=idx;
}
ll dep[N],f[N][19];
ll siz[N],sum[N],sd[N];
//子树大小 1为根i到子树所有节点距离和 i为根到所有节点的距离和 
void dfs(ll u,ll fa)
{dep[u]=dep[fa]+1;siz[u]=1;f[u][0]=fa;for(int i=1;(1<<i)<=dep[u];i++)f[u][i]=f[f[u][i-1]][i-1];for(int i=head[u];i;i=e[i].next){ll v=e[i].to;if(v==fa)continue;dfs(v,u);siz[u]+=siz[v];sum[u]+=sum[v]+siz[v];}
}
void dfs2(ll u,ll fa)
{for(int i=head[u];i;i=e[i].next){ll v=e[i].to;if(v==fa)continue;sd[v]=sd[u]+(n-siz[v])-siz[v];//把v提到根节点 dfs2(v,u);}
}
ll lca(ll x,ll y)
{if(dep[x]<dep[y])swap(x,y);for(int i=18;i>=0;i--)if(dep[x]-(1<<i)>=dep[y])x=f[x][i];if(x==y)return x;for(int i=18;i>=0;i--){if(f[x][i]!=f[y][i]){x=f[x][i];y=f[y][i];}}return f[x][0];
}
ll dis(ll u,ll v)
{return dep[u]+dep[v]-2*dep[lca(u,v)];
}
ll jump(ll u,ll k)
{ll j=0;while(k){if(k&1)u=f[u][j];k>>=1;j++;}return u;
}
int main()
{scanf("%lld",&n);for(int i=1;i<n;i++){ll u,v;scanf("%lld%lld",&u,&v);addedge(u,v);addedge(v,u);}dfs(1,0);sd[1]=sum[1];dfs2(1,0);scanf("%lld",&m);for(int i=1;i<=m;i++){ll L,R;scanf("%lld%lld",&L,&R);if(L==R){printf("%lld\n",sd[L]);continue;}if(dep[L]>dep[R])swap(L,R);//L浅R深 R开始跳 ll Dis=dis(L,R),Mid=jump(R,(Dis-1)/2);//跳到中点Mid lca(L,mid)就是通往1的路径交汇点 ll ans1=sd[L]-sum[Mid]-dis(L,Mid)*siz[Mid];ll ans2=sd[R]-(sd[Mid]-sum[Mid])-dis(R,Mid)*(n-siz[Mid]);printf("%lld\n",ans1+ans2);}return 0;
}
http://www.xdnf.cn/news/6947.html

相关文章:

  • FTP与NFS服务实战:从配置到应用
  • 考公知识总结
  • 怎么用Origin画出MATLAB效果的3D时频图
  • [ctfshow web入门] web77
  • Python基于Django的校园招聘系统【附源码、文档说明】
  • 寻找树的中心(重心)
  • Mysql 索引概述
  • 通过多线程同时获取H264和H265码流
  • 本地缓存更新方案探索
  • 多模态模型如何处理任意分辨率输入——Tiling与Packing技术详解
  • CentOS 下 FTP 与 NFS 服务深度解析:从基础配置到实战应用
  • css 中 content: “\e6d0“ 怎么变成图标的?
  • 2000 元以下罕见的真三色光源投影仪:雷克赛恩Cyber Pro1重新定义入门级投影体验
  • 南航无人机大规模户外环境视觉导航框架!SM-CERL:基于语义地图与认知逃逸强化学习的无人机户外视觉导航
  • STM32F10xx 参考手册
  • ALIENTEK精英STM32F103开发板 实验0测试程序详解
  • 信息安全的基石:深入理解五大核心安全服务
  • NPN、PNP三极管的应用
  • 企业级电商数据对接:1688 商品详情 API 接口开发与优化实践
  • Pandas 掌握Matplotlib基础绘图①
  • 6to4、6over4的类比解释
  • MAUI之XAML标记扩展
  • Linux:计算机的层状结构
  • .NET 中管理 Web API 文档的两种方式
  • 指定elf文件dwarf 版本以及查看dwarf版本号
  • C++ 蓝桥 STEMA 真题模拟测试卷二
  • 程序中断方式好题分享
  • 日志系统**
  • 蓝桥杯11届国B 答疑
  • Redis内存管理深度解析