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;
}