Traffic Network in Numazu
Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 262144/262144 K (Java/Others)
Total Submission(s): 510 Accepted Submission(s): 196
Problem Description
Chika is elected mayor of Numazu. She needs to manage the traffic in this city. To manage the traffic is too hard for her. So she needs your help.
You are given the map of the city —— an undirected connected weighted graph with N nodes and N edges, and you have to finish Q missions. Each mission consists of 3 integers OP, X and Y.
When OP=0, you need to modify the weight of the Xth edge to Y.
When OP=1, you need to calculate the length of the shortest path from node X to node Y.
Input
The first line contains a single integer T, the number of test cases.
Each test case starts with a line containing two integers N and Q, the number of nodes (and edges) and the number of queries. (3≤N≤105)(1≤Q≤105)
Each of the following N lines contain the description of the edges. The ith line represents the ith edge, which contains 3 space-separated integers ui, vi, and wi. This means that there is an undirected edge between nodes ui and vi, with a weight of wi. (1≤ui,vi≤N)(1≤wi≤105)
Then Q lines follow, the ith line contains 3 integers OP, X and Y. The meaning has been described above.(0≤OP≤1)(1≤X≤105)(1≤Y≤105)
It is guaranteed that the graph contains no self loops or multiple edges.
Output
For each test case, and for each mission whose OP=1, print one line containing one integer, the length of the shortest path between X and Y.
Sample Input
2 5 5 1 2 3 2 3 5 2 4 5 2 5 1 4 3 3 0 1 5 1 3 2 1 5 4 0 5 4 1 5 1 5 3 1 2 3 1 3 2 3 4 4 4 5 5 2 5 5 0 1 3 0 4 1 1 1 4
Sample Output
5 6 6 6
Source
2018 Multi-University Training Contest 7
题意: 给n个点和n条边,两种操作,操作0 改变某条边的权值,操作1 询问某两个点的最短路?
思路: n个点,n条边,那么一定是一棵树带1个环,考虑仅仅是树的情况,我可以求个dfs序,然后找到某个点管辖的区间,然后每次更新这条边,就可以用线段树维护这条边所影响的点,那么就是更新这条边下边的点管辖的区间,然后就是求个LCA 得到结果,但是现在问题在于环,其实我可以选择任意一条环上的边,然后考虑去掉这条边,那么就转化成上边的问题了,最后我考虑加上这条边的影响,那么如果u到v经过环的话,那么肯定会多出来两条边,就是u-> uu -> vv->v 和u->vv->uu->u 这两条边,加上这两条边的影响,那么问题就可以解决了。 代码写的跟shi一样。。。
另外测试了一下,数据就三个样例,而且三个样例最后一条边都在环上,所以直接用最后一条边当作环上的边也能过。。。
代码:
#include<bits/stdc++.h>
#define lson (i<<1)
#define rson (i<<1|1)
using namespace std;
typedef long long ll;
const int N =1e5+5;
struct node
{
int u,v,next;
int id,f;
ll w;
}edge[N*2];
struct node1
{
int l,r;
ll w;
ll lz;
}tr[N<<2];
int tot,head[N];
int n,q;
int anc[N<<1][20];
int dfn;
int dfns[N*2];
int dep[N*2];
int pos[N];
int inde[N];
int L[N];
int R[N];
int clo;
int to[N];
int vis[N];
ll ww[N];
int uu,vv;
ll cost;
int huan;
int idd;
void init()
{
tot=0;
memset(head,-1,sizeof(head));
memset(vis,0,sizeof(vis));
memset(inde,-1,sizeof(inde));
memset(pos,-1,sizeof(pos));
clo=0; huan=0; idd=0; dfn=0; /// dfn竟然没清零 MMP
}
void add(int u,int v,ll w,int id)
{
edge[++tot].u=u; edge[tot].v=v; edge[tot].id=id; edge[tot].w=w; edge[tot].f=0;
edge[tot].next=head[u]; head[u]=tot;
}
void dfs1(int u,int fa)
{
if(vis[u]){
uu=fa; vv=u; huan=1; return ;
}
vis[u]=1;
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].v;
if(v==fa) continue;
dfs1(v,u);
}
}
void dfs2(int u,int deep) /// dfs序
{
//cout<<" u "<<u<<" deep "<<deep<<endl;
dfns[dfn]=u; dep[dfn]=deep; pos[u]=dfn++;
L[u]=++clo;
inde[u]=L[u]; /// 记录u在线段树中的位置
for(int i=head[u];i!=-1;i=edge[i].next){
if(edge[i].f) continue; /// 如果是标记的边跳过
int v=edge[i].v;
if(pos[v]==-1){
to[edge[i].id]=v; /// 表示这条边指向哪个点?
dfs2(v,deep+1);
dfns[dfn]=u; dep[dfn++]=deep;
}
}
R[u]=clo;
}
void init_RMQ(int n) /// dfn
{
for(int i=1;i<=n;++i) anc[i][0]=i;
for(int j=1;(1<<j)<=n;++j)
for(int i=1;i+(1<<j)-1<=n;++i){
if(dep[anc[i][j-1]]<dep[anc[i+(1<<(j-1))][j-1]]) anc[i][j]=anc[i][j-1];
else anc[i][j]=anc[i+(1<<(j-1))][j-1];
}
}
inline int RMQ(int L,int R)
{
int k=0;
while(1<<(k+1)<=R-L+1) ++k;
if(dep[anc[L][k]]<dep[anc[R-(1<<k)+1][k]]) return anc[L][k];
return anc[R-(1<<k)+1][k];
}
inline int LCA(int u,int v)
{
if(pos[u]>pos[v]) return dfns[RMQ(pos[v],pos[u])];
return dfns[RMQ(pos[u],pos[v])];
}
void push_up(int i)
{
tr[i].w=tr[lson].w+tr[rson].w;
}
void push_down(int i)
{
if(tr[i].lz){ /// 查询只有点查询,所以不必更新区间点的sum
ll &lz=tr[i].lz;
tr[lson].lz+=lz; tr[rson].lz+=lz;
tr[lson].w+=lz; tr[rson].w+=lz;
lz=0;
}
}
void build(int i,int l,int r)
{
tr[i].l=l; tr[i].r=r; tr[i].w=0; tr[i].lz=0;
if(l==r) return ;
int mid=(l+r)>>1;
build(lson,l,mid);
build(rson,mid+1,r);
}
void update(int i,int l,int r,ll w)
{
if(tr[i].l==l&&tr[i].r==r){
tr[i].lz+=w; tr[i].w+=w;
return ;
}
push_down(i);
int mid=(tr[i].l+tr[i].r)>>1;
if(r<=mid) update(lson,l,r,w);
else if(l>mid ) update(rson,l,r,w);
else{
update(lson,l,mid,w);
update(rson,mid+1,r,w);
}
push_up(i);
}
ll query(int i,int aim)
{
if(tr[i].l==tr[i].r&&tr[i].l==aim){
return tr[i].w;
}
push_down(i);
int mid=(tr[i].l+tr[i].r)>>1;
if(aim<=mid) return query(lson,aim);
else return query(rson,aim);
}
ll getans(int u,int v)
{
int lca=LCA(u,v);
ll sum1,sum2,sum3;
sum1=query(1,L[u]); sum2=query(1,L[v]);
sum3=query(1,L[lca]);
return sum1+sum2-sum3*2;
}
int main()
{
int T;
int u,v,op;
ll w;
scanf("%d",&T);
while(T--)
{
scanf("%d %d",&n,&q);
init();
for(int i=1;i<=n;i++){
scanf("%d %d %lld",&u,&v,&w);
add(u,v,w,i);
add(v,u,w,i);
ww[i]=w;
}
dfs1(1,-1);/// 第一遍dfs 先找到环中的任意一条边
for(int i=1;i<=tot;i++){ /// 给边打上标记
if((edge[i].u==uu&&edge[i].v==vv)||(edge[i].u==vv&&edge[i].v==uu)){
edge[i].f=1;
idd=edge[i].id;
cost=edge[i].w;
}
}
dfs2(1,0);
/*cout<<"dfn "<<dfn<<endl;
cout<<uu<<" *** "<<vv<<endl;
for(int i=1;i<=n;i++){
cout<<"l "<<L[i]<<" r "<<R[i]<<endl;
}
*/
init_RMQ(dfn);
build(1,1,n); /// 以dfs的遍历出的 L,R 建树 那么接下来就是一个区间更新,单点查询的问题了
for(int i=1;i<=n;i++){
if(i==idd) continue;
u=to[i];
update(1,L[u],R[u],ww[i]);
}
/*
for(int i=1;i<=n;i++){
ll tmp=query(1,L[i]);
cout<<" dis "<<tmp<<endl;
}
*/
while(q--)
{
scanf("%d",&op);
if(op==0){
scanf("%d %lld",&u,&w);
if(u==idd){ /// 如果是标记的环中的边,那么就没必要更新线段树
ww[u]=w;
}
else{
int tmp=to[u];
//cout<<"tmp "<<tmp<<" L "<<L[tmp]<<" R "<<R[tmp]<<" w "<<ww[u]<<endl;
update(1,L[tmp],R[tmp],-ww[u]);
ww[u]=w;
update(1,L[tmp],R[tmp],ww[u]);
}
}
else{
scanf("%d %d",&u,&v);
ll ans=getans(u,v);
ans=min(ans,getans(uu,u)+getans(vv,v)+cost); /// 经过标记的路的两个不同的方向。
ans=min(ans,getans(uu,v)+getans(vv,u)+cost);
printf("%lld\n",ans);
}
}
}
return 0;
}