LGP4990 小埋与刺客传奇 学习笔记
LGP4990 小埋与刺客传奇 学习笔记
前言
曾几何时我也是舞线吃啊,但是水平泰国第几,刺客传奇是不可能过的。
现在……舞线诈尸一次后又停更多久了……令人感叹。
以及,很难评价此题的数据强度。说它强还是弱感觉都不合适。只能说古早公开赛题的数据专业程度存疑。
题意简述
给定一个 \(n\) 点 \(m\) 边的简单有向有权图,初始时刻为第 \(0\) 时刻。
之后会有 \(T\) 个事件,第 \(i\) 个事件会在 \(t\) 时刻发生,属于以下两种操作之一:
- 新加入一条编号为 \(m+i\) 的边 \(\{u_i,v_i,w_i\}\)。
- 删除当前未消失的边中编号第 \(k\) 大的边。
问这个图最早在哪个时刻存在 \(1\to n\) 的路径,并求出这个时刻 \(1\to n\) 的最长路长度。
如果始终不存在 \(1\to n\) 的路径,则输出一行Continue from the last checkpoint。
\(n,T\le 10^5,m\le 2\times 10^5\);设有 \(b\) 次删边操作,\(b\le 10^3\)。数据保证不存在正环。
时限 \(\text{3.00s}\)。
做法解析
按时刻排序后,题目的操作序列应该形如:有 \(b\) 次删边操作,每两个删边操作间有若干加边操作。
我们不能忍受每次操作后都重新跑一遍最长路,但我们发现,加边只会让连通性“单调上升”,所以我们对于每一段连续的加边操作,二分地去找它最早连通的时刻。
(至于删除操作,删边后的图连通性肯定劣于删边前,所以执行所有删除操作后都不需要跑最长路)
不过,“当前未消失的第 \(k\) 条边”怎么处理?大致方法是,我们用树状数组:对于边 \(e_i\),存在则 \(v_i=1\),不存在则 \(v_i=0\),并用树状数组维护 \(v_i\) 的前缀和,这样我们就能在树状数组上二分找到第一个满足前缀和大于等于 \(k\) 的地方,其对应的下标就是“当前未消失的第 \(k\) 条边”的编号。
设有 \(b\) 次删边操作,复杂度在 \(O(b\log T\cdot m\log m)\) 左右。
代码实现
小优化:对于每个连续加边段可以先判断一下其加满的时候连不连通,如果连这时都不能联通,显然二分是不用做的。
优化之后复杂度是不是变成 \(O((b+\log T)\cdot m\log m)\) 了。
#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
const int MaxN=1e6+5,MaxM=3e6+5;
const int Inf=0x3f3f3f3f;
int N,M,T,W,X,Y,Z,Opt;
int B[MaxM],flag,ans[2];
struct oper{
int w,o,x,y,z;
friend bool operator<(oper a,oper b){return a.w<b.w;}
}A[MaxM];
int ava[MaxM],dis[MaxN],vis[MaxN],ddis[MaxN];
vector<int> Gr[MaxN];
struct anod{
int u,d;
friend bool operator<(anod a,anod b){return a.d<b.d;}
};
priority_queue<anod> pq;
bool dijk(int tim){
fill(dis,dis+N+1,-1);
fill(vis,vis+N+1,0);
dis[1]=0;pq.push({1,0}),vis[1]=true;
while(!pq.empty()){
auto [u,d]=pq.top();pq.pop();
for(auto &x : Gr[u]){
if(ava[x]==-1||ava[x]>tim)continue;
int v=A[x].y,w=A[x].z;
if(dis[v]<dis[u]+w){
dis[v]=dis[u]+w;
if(!vis[v])vis[v]=1,pq.push({v,dis[v]});
}
}
}
ddis[tim]=dis[N];
return dis[N]>=0;
}
void proceres(int t){if(!flag)flag=1,ans[0]=B[t-M],ans[1]=ddis[t];}
struct BinidTree{
int n,t[MaxM];
void init(int x){n=x,fill(t,t+n+1,0);}
int lowbit(int x){return x&(-x);}
void add(int p,int x){for(;p<=n;p+=lowbit(p))t[p]+=x;}
int gts(int p){int res=0;for(;p;res+=t[p],p-=lowbit(p));return res;}
}BiT;
int main(){
readis(N,M);
for(int i=1;i<=M;i++){
readis(X,Y,Z);
A[i]={0,0,X,Y,Z};
}
readi(T);
for(int i=1;i<=T;i++){
readis(W,Opt),B[i]=W;
if(Opt==0)readis(X,Y,Z);
if(Opt==1)readis(X);
A[M+i]={W,Opt,X,Y,Z};
}
sort(A+M+1,A+M+T+1),sort(B+1,B+T+1),BiT.init(M+T);
for(int i=1;i<=M;i++)BiT.add(i,1);
for(int i=1;i<=T;i++)ava[M+i]=A[M+i].w=M+i;
for(int i=1;i<=M;i++)if(A[i].o==0)Gr[A[i].x].push_back(i);
if(dijk(0)){writil(0),writi(dis[N]);return 0;}
for(int i=M+1,j;i<=M+T&&!flag;){//
if(A[i].o==0){
j=i;while(A[j+1].o==0&&j<=M+T)j++;
for(int k=i;k<=j;k++)BiT.add(k,1),Gr[A[k].x].push_back(k);
if(!dijk(j)){i=j+1;continue;}
int sl=i,sr=j-1,smid,sres=j;
while(sl<=sr){
smid=(sl+sr)>>1;
if(dijk(smid))sres=smid,sr=smid-1;
else sl=smid+1;
}
if(sres)proceres(sres);
i=j+1;
}
else{
int sl=1,sr=i,smid,sres=0;
while(sl<=sr){
smid=(sl+sr)>>1;
if(BiT.gts(smid)>=A[i].x)sres=smid,sr=smid-1;
else sl=smid+1;
}
if(sres)BiT.add(sres,-1),ava[sres]=-1;
i++;
}
}
if(flag)writil(ans[0]),writi(ans[1]);
else puts("Continue from the last checkpoint");
return 0;
}
反思总结
“以删边操作为界分块,每一块内部不断加边是满足单调性的”,这其实还算好想。
更值得积累的一点在于,这题做“当前未消失的第 \(k\) 条边”的做法:树状数组配合二分。
浙公网安备 33010602011771号