NOI2018 D1T1 [NOI2018]归程 解题报告
P4768 [NOI2018]归程
题目描述
本题的故事发生在魔力之都,在这里我们将为你介绍一些必要的设定。
魔力之都可以抽象成一个 \(n\) 个节点、\(m\) 条边的无向连通图(节点的编号从 \(1\) 至 \(n\))。我们依次用 \(l,a\) 描述一条边的长度、海拔。
作为季风气候的代表城市,魔力之都时常有雨水相伴,因此道路积水总是不可避免的。由于整个城市的排水系统连通,因此有积水的边一定是海拔相对最低的一些边。我们用水位线来描述降雨的程度,它的意义是:所有海拔不超过水位线的边都是有积水的。
Yazid是一名来自魔力之都的OIer,刚参加完ION2018的他将踏上归程,回到他温暖的家。
Yazid 的家恰好在魔力之都的 \(1\) 号节点。对于接下来 \(Q\) 天,每一天Yazid 都会告诉你他的出发点 \(v\) ,以及当天的水位线\(p\)。 每一天,Yazid 在出发点都拥有一辆车。这辆车由于一些故障不能经过有积水的边。 Yazid 可以在任意节点下车,这样接下来他就可以步行经过有积水的边。但车会被留在他下车的节点并不会再被使用。
需要特殊说明的是,第二天车会被重置,这意味着:
- 车会在新的出发点被准备好。
- Yazid 不能利用之前在某处停放的车。Yazid 非常讨厌在雨天步行,因此他希望在完成回家这一目标的同时,最小化他步行经过的边的总长度。
请你帮助 Yazid 进行计算。
本题的部分测试点将强制在线,具体细节请见【输入格式】和【子任务】。
输入输出格式
输入格式:
单个测试点中包含多组数据。输入的第一行为一个非负整数\(T\),表示数据的组数。
接下来依次描述每组数据,对于每组数据:
第一行 \(2\) 个非负整数 \(n,m\),分别表示节点数、边数。
接下来 \(m\) 行,每行 \(4\) 个正整数\(u, v, l, a\),描述一条连接节点 \(u, v\) 的、长度为 \(l\)、海拔为 \(a\) 的边。 在这里,我们保证\(1 \leq u,v \leq n\)。
接下来一行 \(3\) 个非负数 \(Q, K, S\) ,其中 \(Q\) 表示总天数,\(K \in {0,1}\) 是一个会在下面被用到的系数,\(S\) 表示的是可能的最高水位线。
接下来 \(Q\) 行依次描述每天的状况。每行 \(2\) 个整数 \(v_0,p_0\)描述一天:
这一天的出发节点为
\(v = (v_0 + K \times \mathrm{lastans} - 1) \bmod n + 1\)。
这一天的水位线为
\(p = (p_0 + K \times \mathrm{lastans}) \bmod (S + 1)\)。
其中 lastans
表示上一天的答案(最小步行总路程)。特别地,我们规定第 \(1\) 天时 lastans = 0。 在这里,我们保证\(1 \leq v_0 \leq n,0 \leq p_0 \leq S\)。
对于输入中的每一行,如果该行包含多个数,则用单个空格将它们隔开。
输出格式:
依次输出各组数据的答案。对于每组数据:
输出 \(Q\) 行每行一个整数,依次表示每天的最小步行总路程。
所有测试点均保证 \(T\leq 3\),所有测试点中的所有数据均满足如下限制:
\(n\leq 2\times 10^5\),\(m\leq 4\times 10^5\),\(Q\leq 4\times 10^5\),\(K\in\left\{0,1\right\}\),\(1\leq S\leq 10^9\)。
对于所有边:\(l\leq 10^4\),\(a\leq 10^9\)。
如果在NOI考场上,这题一定不能挂。
如果你没学过虫狗鼠或者并茶几,那么只要你不写SPFA,在一个多个小时内稳稳拿到离线的65pts是没问题的。
可持久化并茶几我不会,不过虫狗鼠的思路挺简单的,会就是一眼题。
思路:
先按最大生成树建重构树,按照惯例顺手搞一下倍增,然后预处理1为源点的最短路,每次询问直接倍增找到大于水位的节点更新答案即可。
Code:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
const int N=4e5+10;
struct Edge
{
int u,v,w;
bool friend operator <(Edge n1,Edge n2){return n1.w>n2.w;}
}e[N];
int head[N],to[N<<1],Next[N<<1],edge[N<<1],cnt;
void add(int u,int v,int w)
{
to[++cnt]=v,edge[cnt]=w,Next[cnt]=head[u],head[u]=cnt;
}
int F[N],f[N][20],n,m;
int Find(int x){return F[x]=F[x]==x?x:Find(F[x]);}
int dis[N<<1],used[N],poi[N<<1];
int min(int x,int y){return x<y?x:y;}
#define P std::pair <int,int>
std::priority_queue <P,std::vector <P>,std::greater <P> > q;
void disj()
{
memset(dis,0x3f,sizeof(dis));
memset(used,0,sizeof(used));
dis[1]=0;
q.push(std::make_pair(0,1));
while(!q.empty())
{
int u=q.top().second;
q.pop();
if(used[u]) continue;
used[u]=1;
for(int i=head[u];i;i=Next[i])
{
int v=to[i];
if(dis[v]>dis[u]+edge[i])
{
dis[v]=dis[u]+edge[i];
q.push(std::make_pair(dis[v],v));
}
}
}
}
int find(int v,int p)
{
for(int i=19;~i;i--)
if(poi[f[v][i]]>p)
v=f[v][i];
return dis[v];
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
memset(head,0,sizeof(head));
memset(f,0,sizeof(f));
memset(poi,0,sizeof(poi));
cnt=0;
scanf("%d%d",&n,&m);
int n_=n;
for(int w,i=1;i<=m;i++)
{
scanf("%d%d%d%d",&e[i].u,&e[i].v,&w,&e[i].w);
add(e[i].u,e[i].v,w),add(e[i].v,e[i].u,w);
}
for(int i=1;i<=n<<1;i++) F[i]=i;
disj();
std::sort(e+1,e+1+m);
for(int i=1;i<=m;i++)
{
int u=e[i].u,v=e[i].v;
int r1=Find(u),r2=Find(v);
if(r1==r2) continue;
F[r1]=F[r2]=f[r1][0]=f[r2][0]=++n;
poi[n]=e[i].w,dis[n]=min(dis[r1],dis[r2]);
}
for(int j=1;j<=19;j++)
for(int i=1;i<=n;i++)
f[i][j]=f[f[i][j-1]][j-1];
int lastans=0,q,k,s;
scanf("%d%d%d",&q,&k,&s);
for(int v,p,i=1;i<=q;i++)
{
scanf("%d%d",&v,&p);
v=(v+k*lastans-1)%n_+1;
p=(p+k*lastans)%(s+1);
printf("%d\n",lastans=find(v,p));
}
}
return 0;
}
2018.10.17