ATcoder 两题
Atcoder 两题
AT_abc365_f
题目描述(来自谷歌翻译)
平面上有无数个单元格。对于每对整数 \((x,y)\) ,都有一个对应的单元格,我们将其称为单元格 \((x,y)\) 。
每个单元格要么是空单元格,要么是壁单元格。
给定两个长度为 \(N\) 的正整数序列: \(L=(L _ 1,L _ 2,\dotsc,L _ N)\) 和 \(U=(U _ 1,U _ 2,\dotsc,U _ N)\) 。这里, \(L _ i\) 和 \(U _ i\) 满足 \(1\leq L _ i\leq U _ i\leq10 ^ 9\) 和 \(i=1,2,\ldots,N\) 。
所有单元格 \((x,y)\ (1\leq x\leq N,L _ x\leq y\leq U _ x)\) 都是空单元格,所有其他单元格都是壁单元格。
当 Takahashi 位于空单元格 \((x,y)\) 时,他可以执行以下操作之一。
- 如果单元格 \((x+1,y)\) 为空单元格,则移至单元格 \((x+1,y)\) 。
- 如果单元格 \((x-1,y)\) 为空单元格,则移至单元格 \((x-1,y)\) 。
- 如果单元格 \((x,y+1)\) 为空单元格,则移至单元格 \((x,y+1)\) 。
- 如果单元格 \((x,y-1)\) 为空单元格,则移至单元格 \((x,y-1)\) 。
保证他可以通过重复他的操作在任意两个空单元格之间移动。
按照以下格式回答 \(Q\) 个查询。
对于第 \(i\) 个查询 \((1\leq i\leq Q)\) ,您将获得四个整数 \((s _ {x,i},s _ {y,i},t _ {x,i},t _ {y,i})\) 。找出 Takahashi 从单元格 \((s _ {x,i},s _ {y,i})\) 移动到单元格 \((t _ {x,i},t _ {y,i})\) 所需的最少操作数。对于每个查询,保证给定的两个单元格是空单元格。
\(n,Q \leqslant 10^5\) 。
思路点拨
为了方便讨论,默认每一次存在有 \(s_{x,i}<t_{x,i}\) 。
考虑单次询问最简单的怎么做,就是暴力 \(dp\) 了。考虑 \(f_{i,j}\) 表示走到了 \(x=i,y=j\) 的位置所需要的最小花费。转移是简单的:
但是稍微有经验的同学会知道 \(f_i\) 其实是一个折线图函数 \(y=|x-a|\) ,我们关注于 \(a\) 的变化,因为知道了 \(a\) 可以求出整个函数。假设现在我们 \(y\) 所在的区间是 \([L_i,R_i]\) ,\(f_i\) 在 \(a\) 处取最小值,那么当转移到 \(i+1\) 时分类讨论一下:
- \(a \in [L_{i+1},R_{i+1}]\) ,那么 \(a\) 的位置不会发生变化。
- \(a>R_{i+1}\) ,那么 \(a\) 会变成 \(R_{i+1}\) 。
- \(a<L_{i+1}\) , 那么 \(a\) 会变成 \(L_{i+1}\) 。
那么我们就获得了一个 \(O(n)\) 维护 \(a\) 的做法,但是题目需要做到更快。
注意到一个关键的性质:当 \(a\) 经历了依次上述的 \(2\) 或 \(3\) 操作,就会变成 \(L,R\) 之一,但是 \(L,R\) 只有 \(O(n)\) 个,这给了我们很大的优化空间。对于这种流程相对固定的问题,可以考虑倍增优化。
定义倍增数组 \(nxt_{i,j,k=0/1}\) 表示目前 \(x=i\) ,\(y=L_i(k=0)/R_i(k=1)\) ,往后 \(2^j\) 次经历 \(2\) 或 \(3\) 操作会到达哪个位置?作为左端点还是右端点出现?中间的权值消耗是多少?那么我们现在的问题就是如何求出 \(nxt_{i,0,k}\) 。
考虑 \(nxt_{i,0,k}\) 的实际含义就是对于一个元素找到之后第一个 \(p\) 满足 \(a>R_p\) 或者 \(a<L_p\) ,这其实又可以划分为两个小问题。对于 \(a>R_p\) 而言就是查询一个序列中,在某个下标后第一个比某一个权值大的元素的位置,不难使用二分+\(\text{ST}\) 表实现。做到了 \(O(n \log n)\) 预处理,再用 \(O(n \log n)\) 预处理倍增数组。
每一次查询,我们只需要知道 \(s_{y,i}\) 第一次被执行 \(2\) 或者 \(3\) 操作的位置就可以,发现这和预处理倍增数组是一样的。接下来的一部分交给倍增跳到某一个 \((I,L_I)\) 或者 \((I,R_I)\) 。接下来就会一直满足 \(a\in[L,R]\) ,那么加上它与 $(t_{x,i},t_{y,i}) $ 的曼哈顿距离即可。单次查询 \(O(\log n)\) 。
总体时间复杂度 \(O(n \log n)\) ,示范代码为了方便实现了 \(O(n \log^2n)\) 。
示范代码
#include<bits/stdc++.h>
#define int long long
#pragma GCC optimize(2)
using namespace std;
namespace fastIO{
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') f=-f;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int buf[20],TOT;
inline void print(int x,char ch=' '){
if(x<0) putchar('-'),x=-x;
else if(x==0) buf[++TOT]=0;
for(int i=x;i;i/=10) buf[++TOT]=i%10;
do{putchar(buf[TOT]+'0');}while(--TOT);
putchar(ch);
}
}
using namespace fastIO;
const int MAXN=2e5+5;
int n,m,L[MAXN],R[MAXN];
struct node1{
int t[MAXN<<2];
void update(int i,int l,int r,int k,int w){
if(l==r){
t[i]=w;
return ;
}
int mid=(l+r)>>1;
if(k<=mid) update(i<<1,l,mid,k,w);
else update(i<<1|1,mid+1,r,k,w);
t[i]=min(t[i<<1],t[i<<1|1]);
}
int query(int i,int l,int r,int L,int R){
if(L<=l&&r<=R) return t[i];
int mid=(l+r)>>1;
if(R<=mid) return query(i<<1,l,mid,L,R);
if(mid<L) return query(i<<1|1,mid+1,r,L,R);
return min(query(i<<1,l,mid,L,mid),query(i<<1|1,mid+1,r,mid+1,R));
}
}tr,tl;
struct node2{
int t[MAXN<<2];
void update(int i,int l,int r,int k,int w){
if(l==r){
t[i]=w;
return ;
}
int mid=(l+r)>>1;
if(k<=mid) update(i<<1,l,mid,k,w);
else update(i<<1|1,mid+1,r,k,w);
t[i]=max(t[i<<1],t[i<<1|1]);
}
int query(int i,int l,int r,int L,int R){
if(L<=l&&r<=R) return t[i];
int mid=(l+r)>>1;
if(R<=mid) return query(i<<1,l,mid,L,R);
if(mid<L) return query(i<<1|1,mid+1,r,L,R);
return max(query(i<<1,l,mid,L,mid),query(i<<1|1,mid+1,r,mid+1,R));
}
}Tr,Tl;
int nxt_min(int p,int val,node1 &t){
int l=p,r=n+1;
while(l<r){
int mid=(l+r)>>1;
if(t.query(1,1,n+1,p,mid)<val)
r=mid;
else l=mid+1;
}
return l;
}
int nxt_max(int p,int val,node2 &t){
int l=p,r=n+1;
while(l<r){
int mid=(l+r)>>1;
if(t.query(1,1,n+1,p,mid)>val)
r=mid;
else l=mid+1;
}
return l;
}
struct node{
int a,b,val;
node(int x=0,int y=0,int z=0){
a=x,b=y,val=z;
}
}nxt[MAXN][20][2];
signed main(){
n=read();
for(int i=1;i<=n;i++)
L[i]=read(),R[i]=read();
for(int i=1;i<=n;i++){
tr.update(1,1,n+1,i,R[i]);
tl.update(1,1,n+1,i,L[i]);
Tr.update(1,1,n+1,i,R[i]);
Tl.update(1,1,n+1,i,L[i]);
}
tr.update(1,1,n+1,n+1,-1);
tl.update(1,1,n+1,n+1,-1);
Tr.update(1,1,n+1,n+1,1e9+5);
Tl.update(1,1,n+1,n+1,1e9+5);
//小写维护min,大写维护max
for(int i=1;i<=n;i++){
int p1,p2;
p1=nxt_max(i,L[i],Tl);
p2=nxt_min(i,L[i],tr);
if(p1<p2)
nxt[i][0][0]=node(p1,0,(p1-i)+abs(L[p1]-L[i]));
else
nxt[i][0][0]=node(p2,1,(p2-i)+abs(L[i]-R[p2]));
}
for(int i=1;i<=n;i++){
int p1,p2;
p1=nxt_max(i,R[i],Tl);
p2=nxt_min(i,R[i],tr);
if(p1<p2)
nxt[i][0][1]=node(p1,0,(p1-i)+abs(L[p1]-R[i]));
else
nxt[i][0][1]=node(p2,1,(p2-i)+abs(R[i]-R[p2]));
}
nxt[n+1][0][0]=nxt[n+1][0][1]=node(n+1,0,0);
for(int j=1;j<20;j++){
for(int i=1;i<=n+1;i++){
for(int k=0;k<2;k++){
node s1=nxt[i][j-1][k],s2=nxt[s1.a][j-1][s1.b];
nxt[i][j][k]=node(s2.a,s2.b,s1.val+s2.val);
}
}
}
m=read();
while(m--){
int x1=read(),y1=read(),x2=read(),y2=read();
if(x1>x2){
swap(x1,x2);
swap(y1,y2);
}
int p1=nxt_min(x1,y1,tr),p2=nxt_max(x1,y1,Tl);
if(min(p1,p2)>x2) print(x2-x1+abs(y1-y2),'\n');
else{
int ans=0,x,y;
if(p1<p2){
ans+=abs(x1-p1)+abs(y1-R[p1]);
x=p1,y=1;
}
else{
ans+=abs(x1-p2)+abs(y1-L[p2]);
x=p2,y=0;
}
for(int i=19;i>=0;i--){
if(nxt[x][i][y].a<=x2){
node s=nxt[x][i][y];
x=s.a,y=s.b,ans+=s.val;
}
}
y=y?R[x]:L[x];
ans+=abs(x2-x)+abs(y2-y);
print(ans,'\n');
}
}
return 0;
}
AT_abc365_f
题目描述(来自谷歌翻译)
\(N\) 人在 AtCoder 办公室工作。
办公室保存进出记录,自记录开始以来,已有 \(M\) 人进出。
第 \(i\) 条 \((1\leq i\leq M)\) 记录由一对整数 \((T_i, P_i)\) 表示,表示在时间 \(T_i\) ,第 \(P_i\) 人如果在办公室外面,则进入办公室;如果在办公室里面,则离开办公室。
众所周知,记录开始时所有人都在办公室外面,现在他们都在外面。
按照以下格式回答 \(Q\) 个查询。
对于第 \(i\) 个 \((1\leq i\leq Q)\) 个查询,您将获得一对整数 \((A_i, B_i)\) 。找出自记录开始以来第 \(A_i\) 和第 \(B_i\) 个人同时在办公室内的总时长。
\(n,Q \leqslant 2\times 10^5\) 。\(1024\text{MB}\) 。
思路点拨
听说有根号分治的做法会比较简单?这是一份分块做法,还有点复杂。
认为 \(n,m,q\) 同阶。
发现题目的询问比较复杂的样子,不具备什么合并的性质,不好扫描线什么的,所以可以往分块上想,按照时间点离散化后分块,设块长为 \(B\) 。
对于每一个人,它在办公室的时间是一个个区间,并且全部人的区间数量是 \(O(n)\) 级的。对于全部区间而言,它么至多跨过 \(O(n \times \frac{n}{B})\) 个块,我们可以记录每一个块内某一个元素出现了多少长度 \(s_{i,j}\) 。那么在查询时,\(A_i\) 如果在某一个块内完整出现了,那么利用 \(s\) 数组可以 \(O(1)\) 求出答案了。
现在的问题是 \(A_i\) 出现的时间段是零散块的情况了,这种情况对于所有的 \(i\) 出现了至多 \(O(n)\) 次。但是零散块也分了两种情况:
- \(B_i\) 也是零散块。
- \(B_i\) 是完整块。
先解决 \(B_i\) 也在零散块的情况,其实是可以暴力枚举的。因为题目的性质保证了一个块内至多只有 \(O(B)\) 个零散块与其有交,因为同一个左端点至多只有一个块。对于全部的 \(i\) 总共花费了 \(O(nB)\) 的时间。
现在问题就在于 \(B_i\) 也是完整块的情况了,我们不可以枚举 \(A_i\) 的每一个零散块判断相交。原因在于询问时 \(A_i\) 的零散块个数可能到达 \(O(n)\) 级,我们只知道全部 \(i\) 零散块的总数是 \(O(n)\) 级。但是注意到当一个块内 \(B_i\) 是完全覆盖时,我们不关心 \(A_i\) 的零散块在这个区间内的具体端点而是只需要知道在这个块内的的区间并长度就可以了。所以预处理一下是 \(O(n\times \frac{n}{B})\) 。
当 \(B\) 取 \(\sqrt m\) 时,可以做到时间复杂度 \(O(n\sqrt m)\) 。
示范代码
#include<bits/stdc++.h>
//#define int long long
using namespace std;
namespace fastIO{
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') f=-f;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int buf[20],TOT;
inline void print(int x,char ch=' '){
if(x<0) putchar('-'),x=-x;
else if(x==0) buf[++TOT]=0;
for(int i=x;i;i/=10) buf[++TOT]=i%10;
do{putchar(buf[TOT]+'0');}while(--TOT);
putchar(ch);
}
}
using namespace fastIO;
const int MAXN=2e5+5;
int n,m,q,B;
int pre[MAXN],pos[MAXN];
vector<pair<int,int>> e[MAXN],qry[MAXN];
int s[500][MAXN];
struct node{
int l,r,num;
};
vector<node> ins[500];
int bl[MAXN],br[MAXN];
void init(){
int l=1,r=B,id=1;
while(1){
bl[id]=l,br[id]=r;
if(r==m) break;
l=r,r=min(m,r+B);
id++;
}
}
void insert(int ql,int qr,int p){
int l=1,r=B,id=1;
while(1){
if(ql<=l&&r<=qr)
s[id][p]+=pos[r]-pos[l];
else{
int L=max(ql,l),R=min(qr,r);
if(L<R){
ins[id].push_back((node){L,R,p});
s[id][p]+=pos[R]-pos[L];
}
}
if(r==m) break;
l=r,r=min(m,r+B);
id++;
}
}
vector<int> f;
int ans[MAXN],sum[MAXN],g[500];
bool vis[MAXN];
int stk[MAXN],top;
void query(int ql,int qr,int p){
int l=1,r=B,id=1;
while(1){
if(ql<=l&&r<=qr) f.push_back(id);
else{
int L=max(ql,l),R=min(qr,r);
if(L<R){
g[id]+=pos[R]-pos[L];
for(auto I:ins[id]){
int x=max(L,I.l),y=min(R,I.r);
if(x<=y) sum[I.num]+=pos[y]-pos[x];
if(!vis[I.num]) vis[I.num]=1,stk[++top]=I.num;
}
}
}
if(r==m) break;
l=r,r=min(m,r+B);
id++;
}
}
int find(int p){
int sum=0;
for(int i:f) sum+=s[i][p];
int l=1,r=B,id=1;
while(1){
if(pos[r]-pos[l]==s[id][p]) sum+=g[id];
if(r==m) break;
l=r,r=min(m,r+B);
id++;
}
return sum;
}
signed main(){
n=read(),m=read();
B=sqrt(m);
init();
for(int i=1;i<=m;i++){
pos[i]=read();
int id=read();
if(!pre[id]) pre[id]=i;
else e[id].push_back(make_pair(pre[id],i)),pre[id]=0;
}
for(int i=1;i<=n;i++)
for(auto j:e[i]) insert(j.first,j.second,i);
q=read();
for(int i=1;i<=q;i++){
int x=read(),y=read();
qry[x].push_back(make_pair(y,i));
}
for(int i=1;i<=n;i++){
f.clear();
for(auto j:e[i]) query(j.first,j.second,i);
for(auto j:qry[i])
ans[j.second]=sum[j.first]+find(j.first);
while(top){
sum[stk[top]]=vis[stk[top]]=0;
top--;
}
for(int j=1;j<=B+5;j++) g[j]=0;
}
for(int i=1;i<=q;i++) print(ans[i],'\n');
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库