BZOJ4289: PA2012 Tax [题解]

最短路的奇葩建图这种题肯定有很多做法啊!
发表下个人思路(想了半天,被某神仙abmis直接否决,但其实过了)
虽然建成的图并没有本质差别(还慢了很多...),但是思路比较清晰,也比较好想(个人觉得).

某一个点的代价是进入该点与从该点出去的边权较大值,所以单纯的最短路不行,我们考虑一个新的建图方式.
即然每个点代价与选择的边有关,那么似不似可以先决定一个点的权值,然后边权比该权值小的边随便跑.
那么就可以记录连着一个点的所有边权,排个序,设这个点度数为 \(x_i\) ,就分成 \(x_i\) 档,每一档对应着排序后第 \(k\) 小的边权.
这样就可以对每个点拆点,就拆成 \(x_i\) 对点,每一对点分成入点\(inp_x\)和出点\(outp_x\),然后从入点向出点连一条权值为第 \(k\) 小的边权,
表示这个点的代价为blabla,然后\(outp_x\)向外连所有边权小于blabla的边.
上图!(样例那条 3和4 连的边)

但是,很明显,对于每一个档,我们要连接在该档以下的所有边,稍微一卡边数就 \(m^2\) 了,
可以发现,对于一个点的所有档,

1.我们在跑那条从入点到出点的边的之前(也就是新图里边权不为零的边),我们可以向比这个档代价高的所有档移动,(在入点内移动)

也就是说,我们从一个边权比较小的边进来,从一个边权比较大的边出去,然后代价是边权比较大的那个边权.
但是不能向代价小的档移动,因为进来的这个边的边权已经决定了,不可以比这个小.

2.同理,在跑那条边之后,我们也可以向边权比当前边权小的档移动,因为代价都花过了,边权比他小的边怎么不可以跑.

这种情况对应从一个边权大的边进,从边权小的边出.
这样,保证在得到这个点的贡献之前,可以向更高贡献的地方转移,同时,在贡献完代价后,可以向贡献低的档转移,
最短路的性质保证了他不会跑一个贡献比出入边都高的边权,
然后一个点就可以这么连:

这样,每一档只需要向外连这一档对应的边就可以了. 边数就是O(m)了.
(可以顺着思路手画一下样例,这里就不上图了,太乱了,也不好画)
建完图跑dij就行了.
(代码有些地方与思路不太一样,毕竟交的时候又是MLE又是RE又是dij写错T了的,卡常卡了半天)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <vector>
#include <cstring>
#define maxn 201010
#define ll long long
using namespace std;

int s,t;
int n,m;

struct PP
{ 
    int u,v;ll d; 
    bool operator < (PP rhs) const {return d<rhs.d;}
}e[201000];
int cnt;//这个是动态开点
int ds[101000],inp[101000][2],outp[101000][2];
/*
ds度数
inp存入点的编号 outp同理
这里用了滚动数组,因为只连向上一档的点
*/
namespace DIJ //就普通的dij
{//namespace 好评吧!
// 这里所有数组都是卡着最大开的
    ll d[maxn<<2];
    struct cmp { bool operator()(int a,int b) {return d[a]>d[b];} };
    priority_queue<int , vector<int> , cmp> que;
    struct node { int v,nex; ll d; }p[maxn<<4];
    int first[maxn<<2],tot;
    int inque[maxn<<2];
    inline ll dij()
    {
        for(register int i=1;i<=cnt;i++) d[i]=0x7fffffffffll;
        d[s]=0; inque[s]=1;
        que.push(s);
        while(!que.empty())
        {
            int u=que.top(); que.pop(); inque[u]=0;
            for(register int i=first[u];i;i=p[i].nex)
            {
                int v=p[i].v;
                if(d[v]>d[u]+p[i].d)
                {
                    d[v]=d[u]+p[i].d;
                    if(!inque[v]) { inque[v]=1; que.push(v); }
                }
            }
        }
        return d[t];
    }
    inline void add(int fr,int to,ll d)
    {
        p[++tot]=(node) { to,first[fr],d };
        first[fr]=tot;
    }
}
inline void newpoint(int x,ll dis) // 新建一对入点和出点
{
    ds[x]++;
    inp[x][ds[x]&1]=++cnt; outp[x][ds[x]&1]=++cnt;
    int xs=ds[x]&1;
    DIJ::add(inp[x][xs],outp[x][xs],dis);
    if(ds[x]>1)
    {
        DIJ::add(inp[x][xs^1],inp[x][xs],0);
        DIJ::add(outp[x][xs],outp[x][xs^1],0);
    }
    if(x==1 && ds[x]==1) s=inp[x][xs]; // 源点直接连到可以到达1号点所有入点的第1档位置
    if(x==n && ds[x]==1) t=outp[x][xs]; // 理由同上
}
inline void add(int x,int y,ll dis)
{
    newpoint(x,dis); newpoint(y,dis);
    int xs=ds[x]&1,ys=ds[y]&1;
    DIJ::add(outp[x][xs],inp[y][ys],0);
    DIJ::add(outp[y][ys],inp[x][xs],0);
}
int main()
{
    scanf("%d %d",&n,&m);
    for(register int i=1;i<=m;i++)
        scanf("%d %d %lld",&e[i].u,&e[i].v,&e[i].d);
    sort(e+1,e+1+m); // 懒得一个点一个点的排序了,直接全排...
                     // 这么写轻松舒畅,好写多了
    for(register int i=1;i<=m;i++)
        add(e[i].u,e[i].v,e[i].d);
    printf("%lld\n",DIJ::dij());
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/junble19768/p/12147571.html