洛谷 P2483 BZOJ 1975 [SDOI2010]魔法豬學院


題目描述

iPig在假期來到了傳說中的魔法豬學院,開始為期兩個月的魔法豬訓練。經過了一周理論知識和一周基本魔法的學習之后,iPig對豬世界的世界本原有了很多的了解:眾所周知,世界是由元素構成的;元素與元素之間可以互相轉換;能量守恆……。

能量守恆……iPig 今天就在進行一個麻煩的測驗。iPig 在之前的學習中已經知道了很多種元素,並學會了可以轉化這些元素的魔法,每種魔法需要消耗 iPig 一定的能量。作為 PKU 的頂尖學豬,讓 iPig 用最少的能量完成從一種元素轉換到另一種元素……等等,iPig 的魔法導豬可沒這么笨!這一次,他給 iPig 帶來了很多 1 號元素的樣本,要求 iPig 使用學習過的魔法將它們一個個轉化為 N 號元素,為了增加難度,要求每份樣本的轉換過程都不相同。這個看似困難的任務實際上對 iPig 並沒有挑戰性,因為,他有堅實的后盾……現在的你呀!

注意,兩個元素之間的轉化可能有多種魔法,轉化是單向的。轉化的過程中,可以轉化到一個元素(包括開始元素)多次,但是一但轉化到目標元素,則一份樣本的轉化過程結束。iPig 的總能量是有限的,所以最多能夠轉換的樣本數一定是一個有限數。具體請參看樣例。

輸入輸出格式

輸入格式:

 

第一行三個數 N、M、E 表示iPig知道的元素個數(元素從 1 到 N 編號)、iPig已經學會的魔法個數和iPig的總能量。

后跟 M 行每行三個數 $s_i$、$t_i$、$e_i$ 表示 iPig 知道一種魔法,消耗 $e_i$ 的能量將元素 $s_i$ 變換到元素 $t_i$ 。

 

輸出格式:

 

一行一個數,表示最多可以完成的方式數。輸入數據保證至少可以完成一種方式。

 

輸入輸出樣例

輸入樣例#1:
4 6 14.9
1 2 1.5
2 1 1.5
1 3 3
2 3 1.5
3 4 1.5
1 4 1.5
輸出樣例#1:
3

說明

有意義的轉換方式共4種:

1->4,消耗能量 1.5
1->2->1->4,消耗能量 4.5
1->3->4,消耗能量 4.5
1->2->3->4,消耗能量 4.5

顯然最多只能完成其中的3種轉換方式(選第一種方式,后三種方式仍選兩個),即最多可以轉換3份樣本。 如果將 E=14.9 改為 E=15,則可以完成以上全部方式,答案變為 4。

數據規模

占總分不小於 10% 的數據滿足 $N <= 6$,$M<=15$。

占總分不小於 20% 的數據滿足 $N <= 100$,$M<=300$,$E<=100$且$E$和所有的$e_i$均為整數(可以直接作為整型數字讀入)。

所有數據滿足 $2 <= N <= 5000$,$1 <= M <= 200000$,$1<=E<=10^7$,$1<=e_i<=E$,$E$和$e_i$為實數。

 

解題思路

  //本來想寫一題左偏樹練練手速的,百度一下就找到了這題,結果似乎沒必要用左偏樹。。。只是因為BZOJ很喪病,卡優先隊列的內存,大家就紛紛選擇了手寫堆,某些大牛選擇了左偏樹當做可持久化堆,動態開點節省空間,而我這等蒟蒻用恆定大小的手寫二叉堆的就夠了…………

  回歸正題。

  這題很容易看出來是求前k短路,是路徑權值之和小於等於E,因為一種轉化方式完成后就不能再用了,而為了盡量多地完成轉換,肯定要選擇當前轉換代價最小(最短路、次短路、第3短路……),轉換完成一次(路徑到達n點一次)就統計答案ans++,最后輸出答案。

  找k短路的算法最容易想到的是BFS暴搜,第k次搜到n點就是k短路。通過 百度一下 查閱資料我們學到了一種名叫A*的算法,用它可以求第K短路,證明不會,但是板子挺好背的,看幾遍也就記住了……(2019年01月27日 更新 放一個月時間也就忘了)

 

  A*有一個東西叫估價函數$f(n)$,$f(n)=g(n)+h(n)$,在求k短路的問題中,$g(n)$是從起點出發已經走了的長度,$h(n)$是從這個點到終點的最短路。

  由於用到每個點到終點的最短路,我們可以在反向圖上跑一遍spfa,求出終點到每個點的最短路,用dis[i]表示終點到第$i$號點的最短距離。

 

  然后就可以跑A*了。

  建立一個優先隊列,每個元素為{d,u},d=f(u),優先隊列是以d為鍵值的小根堆。

  首先把{dis[1],1}/*即第一個點*/加入優先隊列,然后進入如下循環——

    (1) 從優先隊列中取出d最小的節點u;

    (2)如果u是終點n,那么就找到了一條k短路(第k次取出n點時的d就是第k短路路徑長度),E-=d,如果此時剩余的E>=0(浮點數判斷可以用$1e-6$),也就是有足夠能量進行此次轉換,那么ans++,否則能量不足,退出A*;

    (3)拓展正向圖中與u直接相連的點,並將它們入隊(壓入堆中),設當前與u相連的點為v,邊權為w,則新的$d=f(v)=f(u)-dis[u]+w+dis[v]$;

    (4)如果堆空了,就退出A*,否則返回(1);

 ————————2018年5月13日更新————————

  今天發現,這題在洛谷上被hack了……下面是92分代碼,至於AC代碼……我留坑吧

————————2019年10月6日更新————————

  https://www.luogu.org/blog/cjyl/solution-p2483

  原來說可並堆是因為要可持久化進行優化啊……現在看來下面的代碼就是暴力……什么A*嘛

源代碼

#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

int n,m,ans=0;
double E;

struct Edge{
    int next,to;
    double w;
}fe[200010],e[200010];//e存正向圖,fe存反向圖
int head[200010]={0},cnt=1,fhead[200010]={0},fcnt=1;//帶f的都用於存反向圖
void add(int u,int v,double w)
{
    e[cnt].to=v;
    e[cnt].w=w;
    e[cnt].next=head[u];
    head[u]=cnt++;//給正向圖加邊

    fe[fcnt].to=u;
    fe[fcnt].w=w;
    fe[fcnt].next=fhead[v];
    fhead[v]=fcnt++;//給反向圖加邊
}

double dis[5010];//裸spfa
bool inq[5010]={0};
void spfa()
{
    for(int i=0;i<5010;i++) dis[i]=999999999.0;
    dis[n]=0.0;
    queue<int> q;
    q.push(n);
    inq[n]=1;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        inq[u]=0;
        for(int i=fhead[u];i;i=fe[i].next)
        {
            int v=fe[i].to;
            double w=fe[i].w;
            if(dis[v]>dis[u]+w)
            {
                dis[v]=dis[u]+w;
                if(!inq[v])
                {
                    inq[v]=1;
                    q.push(v);
                }
            }
        }
    }
}


struct Heap{
    double d;
    int u;
    bool operator > (const Heap &a)const{
        return d>a.d;
    }
}heap[2000010],temp;//手敲優先隊列
int sz=1;//優先隊列內元素數量+1,個人比較喜歡這種表示方法
void pop()//刪除堆頂,取出堆頂直接用heap[1]即可,我沒寫在pop()里
{
    sz--;
    heap[1]=heap[sz];
    heap[sz]={0,0};
    int the=1,son=2;
    while(son<sz)
    {
        if(heap[son]>heap[son+1]&&son+1<sz) son++;
        if(heap[the]>heap[son]) swap(heap[the],heap[son]);
        else break;
        the=son;
        son=the<<1;
    }
}
void push(double dd,int uu)//加入一個元素,dd=f(uu),dd、uu防止變量名沖突
{
    heap[sz]={dd,uu};
    int the=sz++,fa=the>>1;
    while(fa)
    {
        if(heap[fa]>heap[the]) swap(heap[the],heap[fa]);
        else break;
        the=fa;
        fa>>=1;
    }
}

void astar()
{
    push(dis[1],1);
    while(sz>1)
    {
        int u=heap[1].u;
        double dist=heap[1].d;//取出堆頂
        pop();//刪除堆頂
        if(u==n)//n點出隊,說明找到一條k短路
        {
            E-=dist;
            if(E>=1e-6) ans++;
            else return;
            continue;
        }
        for(int i=head[u];i;i=e[i].next)//拓展與u相連的節點
        {
            int v=e[i].to;
            double w=e[i].w;
            push(dist-dis[u]+w+dis[v],v);
        }
    }
}

int main()
{
    //freopen("test.in","r",stdin);
    scanf("%d%d%lf",&n,&m,&E);
    for(int i=1,u,v;i<=m;i++)
    {
        double w;
        scanf("%d%d%lf",&u,&v,&w);
        add(u,v,w);
    }
    spfa();
    astar();
    printf("%d\n",ans);
    return 0;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM