2018.06.29 NOIP模拟 边的处理(分治+dp)

边的处理(side.cpp)
【问题描述】
有一个 n 个点的无向图,给出 m 条边,每条边的信息形如 < x , y , c , r >
给出 q 组询问形如 < u , v , l , r > 。接下来解释询问以及边的意义。询问表示,一开始你在点 u 上,然后按顺序处理编号从 l 到 r 的边。
对于一条边 < x , y , c , r > ,你可以进行两种操作:
1、如果你当前在 x 点或者 y 点上,那么你可以走这条边(从 x 到 y 或从 y 到 x)并付
出 c 的代价(当然你也可以不走,看操作 2)。
2、如果你不走这条边或者不可以走这条边(即你当前不在 x 或 y 上),那么你需要付出
r 的代价。
询问如果要从 u 点开始,按顺序处理完编号从 l 到 r 的边之后到达 v 点的最小代价,如
果不能到达 v,那么输出-1。
边和点的编号从 1 开始。
【输入格式】
第一行三个数表示 n,m,q。
接下来 m 行,每行四个整数 x,y,c,r 描述一条边。
接下来 q 行,每行四个整数 u,v,l,r 描述一组询问。
【输出格式】
输出共 q 行,每行一个数表示对应询问的答案。
【样例输入 1】
5 5 3
1 4 4 5
4 1 6 1
2 1 2 9
2 5 1 0
1 5 2 5
2 2 2 4
5 4 5 5
1 5 2 5
【样例输出 1】
10
-1
9
【样例输入 2】
4 8 6
2 4 5 8
2 4 4 8
2 3 6 4
1 4 5 0
2 4 10 10
1 3 5 2
3 2 2 9
3 4 1 1
3 2 1 5
3 1 2 2
1 1 1 7
2 3 2 4
3 3 1 7
1 2 2 5
【样例输出 2】
32
-1
41
14
36
27
【数据规模与约定】
对于 20%的数据:n≤10;m≤1000;q≤1000。
对于 40%的数据:n≤10;m≤10000;q≤30000。
对于 60%的数据:n≤20;m≤10000;q≤30000。
对于 80%的数据:n≤25;m≤10000;q≤200000。
对于 100%的数据:n≤30;m≤20000;q≤200000。
对于 100%的数据:c,r 的取值范围是[0,10000]。


考试的时候读完题就感觉凉了,本蒟蒻和 y k 神仙一起尝试暴力骗 20 分, y k d p 轻松过掉前 4 个点,然而我连样例都没跑过,真是丢脸。

相信大家读完题后大家都能判断这是一道玄学 d p ,但再一看数据范围,大家估计都想大骂出题人毒瘤了。言归正传,这题怎么做呢?准确的说,这题如何优化 d p 呢?

好吧我们需要上一波分治,我们假设当前正在处理边的范围在 l , r 之间的询问,区间的中点为 m i d 那么对于每个询问,它的左右端点有三种情况

  1. 第一种情况:询问的右端点在 m i d 的左边。
  2. 第二种情况:询问的区间恰好包含了 m i d
  3. 第三种情况:询问的左端点在 m i d 的右边。

对于情况一和情况三,显然我们可以递归处理,对于情况二,就要用到我们的 d p 了,由于题目上要求我们需要按序处理每条边。所以我们可以用 D P 得到 l f [ i ] [ x ] [ y ] 表示从 x 出发,处理了从 i m i d 的边之后到达 y 的最小代价,以及 r f [ i ] [ x ] [ y ] 表示从 x 出发,处理了从 m i d + 1 i 的边之后到达 y 的最小代价,查询的时候只要枚举一下中间断点 k ,然后执行状态转移方程 a n s [ q [ i ] . i d ] = m i n ( a n s [ q [ i ] . i d ] , l f [ q [ i ] . l ] [ k ] [ q [ i ] . u ] + r f [ q [ i ] . r ] [ k ] [ q [ i ] . v ] ) 就行了 。

怎么求 l f r f 呢?我们可以对于每一个点先假设它不走新的边,那么 l f [ i ] [ u ] [ v ] = l f [ i + 1 ] [ u ] [ v ] + e [ i ] . r ; 并且 r f [ i ] [ u ] [ v ] = r f [ i 1 ] [ u ] [ v ] + e [ i ] . r ; ,如果这个点可以走新的边,那么我们用这条边连接的另外一个点的 l f r f 的值来更新当前点的,也就是 l f [ i ] [ u ] [ e [ i ] . y ] = m i n ( l f [ i ] [ u ] [ e [ i ] . y ] , l f [ i + 1 ] [ u ] [ e [ i ] . x ] + e [ i ] . c ) ; l f [ i ] [ u ] [ e [ i ] . x ] = m i n ( l f [ i ] [ u ] [ e [ i ] . x ] , l f [ i + 1 ] [ u ] [ e [ i ] . y ] + e [ i ] . c ) ; r f [ i ] [ u ] [ e [ i ] . y ] = m i n ( r f [ i ] [ u ] [ e [ i ] . y ] , r f [ i 1 ] [ u ] [ e [ i ] . x ] + e [ i ] . c ) ; r f [ i ] [ u ] [ e [ i ] . x ] = m i n ( r f [ i ] [ u ] [ e [ i ] . x ] , r f [ i 1 ] [ u ] [ e [ i ] . y ] + e [ i ] . c ) ;

另一方面,为了给下一层的递归做准备,我们可以类比整体二分的思想,利用辅助数组重新排列修改过的 q 的顺序,实现细节详见代码。


代码如下:

#include<bits/stdc++.h>
#define Q 200005
#define M 20005
#define N 35
#define inf 0x3f3f3f3f
using namespace std;
int n,m,t,lf[M][N][N],rf[M][N][N],ans[Q];
struct node{int x,y,r,c;}e[M];
struct Node{int u,v,l,r,id;}q[Q],tmp[Q];
inline void solve(int l,int r,int ql,int qr){
    if(ql>qr)return;
    if(l==r){
        for(int i=ql;i<=qr;++i){
            ans[q[i].id]=inf;
            if((q[i].u==e[l].x&&q[i].v==e[l].y)||(q[i].u==e[l].y&&q[i].v==e[l].x))ans[q[i].id]=e[l].c;
            if(q[i].u==q[i].v)ans[q[i].id]=min(ans[q[i].id],e[l].r);
        }
        return;
    }
    int mid=l+r>>1;
    for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)
            lf[mid+1][i][j]=rf[mid][i][j]=inf;
    for(int i=1;i<=n;++i)lf[mid+1][i][i]=rf[mid][i][i]=0;
    for(int i=mid;i>=l;--i){
        for(int u=1;u<=n;++u)for(int v=1;v<=n;++v)lf[i][u][v]=lf[i+1][u][v]+e[i].r;
        for(int u=1;u<=n;++u){
            lf[i][u][e[i].x]=min(lf[i][u][e[i].x],lf[i+1][u][e[i].y]+e[i].c);
            lf[i][u][e[i].y]=min(lf[i][u][e[i].y],lf[i+1][u][e[i].x]+e[i].c);
        }
    }
    for(int i=mid+1;i<=r;++i){
        for(int u=1;u<=n;++u)for(int v=1;v<=n;++v)rf[i][u][v]=rf[i-1][u][v]+e[i].r;
        for(int u=1;u<=n;++u){
            rf[i][u][e[i].x]=min(rf[i][u][e[i].x],rf[i-1][u][e[i].y]+e[i].c);
            rf[i][u][e[i].y]=min(rf[i][u][e[i].y],rf[i-1][u][e[i].x]+e[i].c);
        }
    }
    for(int i=ql;i<=qr;++i)
        if(q[i].l<=mid&&mid<q[i].r)
            for(int k=1;k<=n;++k)
                ans[q[i].id]=min(ans[q[i].id],lf[q[i].l][k][q[i].u]+rf[q[i].r][k][q[i].v]);
    int sl=ql-1,sr=qr+1;
    for(int i=ql;i<=qr;++i)tmp[i]=q[i];
    for(int i=ql;i<=qr;++i)if(tmp[i].r<=mid)q[++sl]=tmp[i];
    for(int i=qr;i>=ql;--i)if(tmp[i].l>mid)q[--sr]=tmp[i];
    solve(l,mid,ql,sl),solve(mid+1,r,sr,qr);
}
int main(){
    memset(ans,inf,sizeof(ans));
    scanf("%d%d%d",&n,&m,&t);
    for(int i=1;i<=m;++i)scanf("%d%d%d%d",&e[i].x,&e[i].y,&e[i].c,&e[i].r);
    for(int i=1;i<=t;++i)scanf("%d%d%d%d",&q[i].u,&q[i].v,&q[i].l,&q[i].r),q[i].id=i;
    solve(1,m,1,t);
    for(int i=1;i<=t;++i)cout<<((ans[i]==inf)?-1:ans[i])<<'\n'; 
    return 0;
}

猜你喜欢

转载自blog.csdn.net/dreaming__ldx/article/details/80861616