[BZOJ3415][POI2013]Price List(图论删边)

一道图论题目,原以为简单却隐藏复杂性。从一个点出发有三种走法:一步步走a,合并b边为a,或特殊情况的走法。暴力解法无法通过,需要优化。优化方法包括确保每个点只被更新一次,避免形成更新环,并在更新后删除无用边以减少枚举。提供了问题的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:

我是超链接

题解:

本来以为是一道sb题然后WA过了才知道是大神题。。。

到达一个点有三种走法(窝一开始以为是两种)
1、一步步走a到达
2、当b < 2a,可以把两条a缩成一条b,如果是奇数就先走a
3、当b < a,这是一种很奇怪的走法,可以通过走更多的边数来换取较少的距离
这里写图片描述
比如这个图,到达A可以走a+b,也可以走2b

我们首先考虑暴力,就是当枚举到一个点,首先一边遍历与他相邻的点,对于这些点再二次遍历相邻的点,这次相邻的点可以用这个枚举点+b的长度更新

当然这样是过不去的,我们还需要优化。
首先每个点只会被更新一次,只有被更新点才有机会继续更新别的点。被更新一次是必然的,不然形成一个更新环就GG了

然后对于一组点A-x-y,可能会经历这样的过程,更新了y之后y入队,y反向边找到x,x又找到y,诚然这不会让答案错误,但是速度会慢,M^2的效率,我们可以考虑在更新的y之后把x-y这条边删掉,这样就不会进一步无用枚举了

代码:

#include <queue>
#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int N=200005;
#define INF 1e9
int tot,nxt[N*2],point[N],v[N*2],dis[N],deep[N],ans[N],d[N];bool vis[N];
int tot1,nxt1[N*2],point1[N],v1[N*2],pre[N*2];
void addline(int x,int y)
{
    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;
    ++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x;
}
void addline1(int x,int y)
{
    ++tot1; nxt1[tot1]=point1[x]; pre[point1[x]]=tot1; point1[x]=tot1; v1[tot1]=y;
    ++tot1; nxt1[tot1]=point1[y]; pre[point1[y]]=tot1; point1[y]=tot1; v1[tot1]=x;
}
void del(int x,int i)
{
    if(i==point1[x]) point1[x]=nxt1[i];
    nxt1[pre[i]]=nxt1[i];
    pre[nxt1[i]]=pre[i];
}
int main()
{
    int n,m,k,a,b;
    scanf("%d%d%d%d%d",&n,&m,&k,&a,&b);
    for (int i=1;i<=m;i++)
    {
        int x,y;scanf("%d%d",&x,&y);
        addline(x,y);addline1(x,y);
    }
    memset(deep,0x7f,sizeof(deep));
    deep[k]=0;memset(vis,0,sizeof(vis));
    queue<int>q;q.push(k);int maxx=0;
    while (!q.empty())
    {
        int now=q.front(); q.pop(); vis[now]=0;
        for (int i=point[now];i;i=nxt[i])
          if (deep[v[i]]>deep[now]+1)
          {
            deep[v[i]]=deep[now]+1;
            maxx=max(maxx,deep[v[i]]);
            if (!vis[v[i]]) vis[v[i]]=1,q.push(v[i]);
          }
    }
    int base=min(2*a,b);
    for (int i=1;i<=maxx;i++)
      dis[i]=(i/2)*base+(i%2?a:0);
    for (int i=1;i<=n;i++) 
      if (deep[i]<INF) ans[i]=dis[deep[i]];
    if (b<a)
    {
        q.push(k);
        while (!q.empty())
        {
            int now=q.front(); q.pop();
            for (int i=point[now];i;i=nxt[i]) vis[v[i]]=1;
            for (int i=point[now];i;i=nxt[i])
              for (int j=point1[v[i]];j;j=nxt1[j])
              {
                if (d[v1[j]] || v1[j]==k || vis[v1[j]]) continue;
                d[v1[j]]=d[now]+1;del(v[i],j);
                q.push(v1[j]); 
              }
            for (int i=point[now];i;i=nxt[i]) vis[v[i]]=0;
        }
    }
    for (int i=1;i<=n;i++)
      if (d[i]) ans[i]=min(ans[i],d[i]*b); 
    for (int i=1;i<=n;i++)
      printf("%d\n",ans[i]); 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值