日常失了智的比賽01


核老闆早就說了這是圖論專練,所以算法框架上應該往圖論上面靠。...大概吧。

比賽的時候全程進入了一種迷離的狀態,有點暈,估計是今天凌晨4點鐘起來看雨結果沒睡好。(MD我爲什麼要這麼早起來看雨)


  • T1

    修公路
    時間限制 : - MS 空間限制 : 65536 KB
    評測說明 : 時限1000ms
    問題描述

    某島國有n個小島構成(編號1到n),該國政府想要通過n-1條雙向公路將這些小島連接起來,使得任意兩個島嶼之間都能相互到達。公路有兩種,一種是高速公路,車速快,建設花費大;另一種是普通公路,車速慢,建設花費少。該國政府不想在一條公路上花費過多的錢,但又要求修建的公路中至少有k條高速公路。所以政府希望,在滿足上述條件的情況下,使得最貴的一條公路花費儘可能少,請你幫忙計算出其中最貴一條公路的價格。

    輸入格式

    第一行,三空格間隔的整數n,k,m,其中m表示有m對島嶼間可以修路。
    接下來以下的m行,每行四個正整數a,b,c1,c2 表示在島嶼a與b 之間修高速公路花費c1塊錢,修普通公路,花費c2塊錢。

    輸出格式

    一個整數表示最貴那條公路的費用。

    樣例輸入 1

    4 2 5
    1 2 6 5
    1 3 3 1
    2 4 6 1
    3 4 4 2

    樣例輸出 1

    4

    樣例輸入 2

    4 1 5
    1 2 6 5
    1 3 3 1
    2 3 9 4
    2 4 6 1
    3 4 4 3

    樣例輸出 2

    3

    樣例輸入 3

    10 4 19
    3 9 6 3
    1 3 4 1
    5 3 10 2
    8 9 8 7
    6 8 8 3
    7 1 3 2
    4 9 9 5
    10 8 9 1
    2 6 9 1
    6 7 9 8
    2 6 2 1
    3 8 9 5
    3 2 9 6
    1 6 10 3
    5 6 3 1
    2 7 6 1
    7 8 6 2
    10 9 2 1
    7 1 10 2

    樣例輸出 3

    5

    提示

    對於30%的數據, 1<=n<=1000           0<=k<=10             n-1<=m<=3000
    

    對於100%的數據,1<=n<=10000 0<=k<=n-1 n-1<=m<=20000

稍有常識的人都能看出這是一道MST的題,果斷Kruskal。再一看,m<=20000喜大普奔,再一看,最大值最小,二分。
二分最長邊就可以了,所以在實際編寫過程中沒有必要去將邊排序。!就r-1,不然就l+1。
以上是正常的正解。
可是比賽的時候失了智,二分的高速公路條數,MDZZ想了半天也沒想出來怎麼證是對的,然後亂搞就出正解了。結果估計是提交的時候太激動了,然後就把二分裏的l=mid+1的l寫成r。gg well play。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <iomanip>
#define mid (l+r>>1)
using namespace std;
inline int R(){
    char t=getchar();int o=0;bool F=0;
    while(t<48||t>57)F|=t==45,t=getchar();
    for(;t<58&&t>47;t=getchar())o=(o<<1)+(o<<3)+t-48;
    return F?-o:o;
}
const int N = 10005, M = 20005;
int n, k, m, fa[N];

struct node1{
    int x, y, a, b, id;
}e[M];
struct node2{
    int x, y, a, b, id;
}w[M];
bool cmp1( node1 q, node1 w ){ return q.a == w.a? q.b > w.b : q.a < w.a; }
bool cmp2( node2 q, node2 w ){ return q.b < w.b; }
bool f[M];
int ans = 0x3fffffff;
int getfa( int x ){ return x == fa[x] ? x : fa[x] = getfa( fa[x] ); }
bool kruskal( int p ){
    int cnt = 0, tmp = 0, tot = 0;
    for( int i = 1; i <= m; i ++ )
        f[i] = 0;
    for( int i = 1; i <= n; i ++ )
        fa[i] = i;
    while( cnt < p ){
        int fx = getfa( e[++tmp].x ), fy = getfa( e[tmp].y );
        if( fx != fy ){
            fa[fx] = fy;
            f[e[tmp].id] = 1;
            cnt ++;
            tot = max( tot, e[tmp].a );
        }
    }
    tmp = 0;
    while( cnt < n - 1 ){
        if( f[w[++tmp].id] ) continue;
        int fx = getfa( w[tmp].x ), fy = getfa( w[tmp].y );
        if( fx != fy ){
            fa[fx] = fy;
            f[w[tmp].id] = 1;
            cnt ++;
            tot = max( tot, w[tmp].b );
        }
    }
    if( tot >= ans ) return 0;
    if( cnt == n-1 ){
        ans = tot;
        return 1;
    }
    return 0;
}

int main()
{
//  freopen("road4.in","r",stdin);
//  freopen("road.out","w",stdout);
    n = R(); k = R(); m = R();
    for( int i = 1; i <= m; i ++ ){
        w[i].x = e[i].x = R(); w[i].y = e[i].y = R();
        w[i].a = e[i].a = R(); w[i].b = e[i].b = R();
        f[m] = 1; w[i].id = e[i].id = i;
    }
    sort( e + 1, e + m + 1, cmp1 );
    sort( w + 1, w + m + 1, cmp2 );
    int l = k, r = n - 1;
    while( l <= r ){
        if( kruskal( mid ) ) r = mid - 1;
        else l = mid + 1;
    }
    kruskal( l );   
    cout << ans;
    return 0;
}
  • T2

    【USACO 2015 Jan Gold】牧草鑑賞家
    時間限制 : 1000 MS 空間限制 : 65536 KB
    問題描述

    約翰有n塊草場,編號1到n,這些草場由若干條單行道相連。奶牛貝西是美味牧草的鑑賞家,她想到達儘可能多的草場去品嚐牧草。

    貝西總是從1號草場出發,最後回到1號草場。她想經過儘可能多的草場,貝西在通一個草場只吃一次草,所以一個草場可以經過多次。因爲草場是單行道連接,這給貝西的品鑑工作帶來了很大的不便,貝西想偷偷逆向行走一次,但最多只能有一次逆行。問,貝西最多能吃到多少個草場的牧草。

    輸入格式

    第一行,兩個整數N和M(1<=N,M<=100000)
    接下來M行,表示有M條單向道路,每條道路有連個整數X和Y表示,從X出發到達Y。

    輸出格式

    一個整數,表示所求答案

    樣例輸入

    7 10
    1 2
    3 1
    2 5
    2 4
    3 7
    3 5
    3 6
    6 5
    7 2
    4 7

    樣例輸出

    6

    提示

    貝西的行走線路是1, 2, 4, 7, 2, 5, 3, 1 ,在5到3的時候逆行了一次。

第一反應就是SCC,肯定是SCC沒準了,然後就覺得SCC之後沒法做,想到了暴力加邊,但是這樣並不能滿足只能走一次的條件。然後我就刪了開始寫SPFA,結果覺得有環啊怎麼辦啊(╯°口°)╯(┴—┴
然後才發現MDZZ_(:з」∠)
SCC縮點之後構正反圖跑最長路,都是從id[1]開始,反圖就是能到達id[1]的點,同時開始的時候dis[id[1]]=0,比較方便後面討論。然後就是枚舉每一條新邊,答案就是MAX( dis[x] + rdis[y], rdis[x] + dis[y] ) + id[1]的大小( dis[x] != -1 && dis[y] != -1 ) dis初值爲-1是有必要的。n+2km的複雜度是可以承受的。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <queue>
#include <vector>
#include <stack>
using namespace std;
inline int R(){
    char t=getchar();int o=0;bool F=0;
    while(t<48||t>57)F|=t==45,t=getchar();
    for(;t<58&&t>47;t=getchar())o=(o<<1)+(o<<3)+t-48;
    return F?-o:o;
}
inline int Min(int x,int y){
    int m=(x-y)>>31;
    return x&m|y&~m;
}
inline int Max(int x,int y){
    int m=(x-y)>>31;
    return y&m|x&~m;
}
const int N = 100005;
int aLast[N], aNext[N], aEnd[N], acnt_edge = 1;
int Last[N], Next[N], End[N], Len[N], cnt_edge = 1;
int rLast[N], rNext[N], rEnd[N], rLen[N], rcnt_edge = 1;

void add1( int a, int b ){
    aEnd[++acnt_edge] = b;
    aNext[acnt_edge] = aLast[a];
    aLast[a] = acnt_edge;
}

void add_edge( int a, int b, int c ){
    End[++cnt_edge] = b;
    Next[cnt_edge] = Last[a];
    Last[a] = cnt_edge;
    Len[cnt_edge] = c;
}
void add_redge( int a, int b, int c ){
    rEnd[++rcnt_edge] = b;
    rNext[rcnt_edge] = rLast[a];
    rLast[a] = rcnt_edge;
    rLen[rcnt_edge] = c;
}
int n, m, scc;
int dis[N], rdis[N], dfs_clock, dfn[N], low[N], Size[N];
int id[N];
bool f[N], inst[N];

queue<int> q;
stack<int> s;
vector<pair<int,int> > v;

void tarjan( int p ){
    dfn[p] = low[p] = ++dfs_clock;
    s.push( p ); inst[p] = 1;
    for( int i = aLast[p], y = aEnd[i]; i; i = aNext[i], y = aEnd[i] )
        if( ! dfn[y] ) tarjan( y ), low[p] = Min( low[p], low[y] );
        else if( inst[y] ) low[p] = Min( low[p], dfn[y] );
    if( dfn[p] == low[p] ){
        int y; scc ++;
        do{
            y = s.top(); s.pop(); inst[y] = 0;
            id[y] = scc; Size[scc] ++;
        }while( p != y );
    }
}

void SPFA( int s ){
    for( int i = 1; i <= scc; i ++ ) dis[i] = -1;
    dis[s] = 0;
    q.push( s ); f[s] = 1;
    while( ! q.empty() ){
        int x = q.front(); q.pop(); f[x] = 0;
        for( int i = Last[x], y = End[i]; i; i = Next[i], y = End[i] )
            if( dis[y] < dis[x] + Len[i] ){
                dis[y] = dis[x] + Len[i];
                if( ! f[y] ){
                    f[y] = 1;
                    q.push(y);
                }
            }
    }
}
void rSPFA( int s ){
    for( int i = 1; i <= scc; i ++ ) rdis[i] = -1;
    rdis[s] = 0;
    q.push( s ); f[s] = 1;
    while( ! q.empty() ){
        int x = q.front(); q.pop(); f[x] = 0;
        for( int i = rLast[x], y = rEnd[i]; i; i = rNext[i], y = rEnd[i] )
            if( rdis[y] < rdis[x] + rLen[i] ){
                rdis[y] = rdis[x] + rLen[i];
                if( ! f[y] ){
                    f[y] = 1;
                    q.push(y);
                }
            }
    }
}


int main()
{
//  freopen("14.in","r",stdin);
    n = R(); m = R();
    int a, b;
    for( int i = 1; i <= m; i ++ ){
        a = R(); b = R();
        add1( a, b );
    }
    for( int i = 1; i <= n; i ++ )
        if( ! dfn[i] ) tarjan( i );

    for( int x = 1; x <= n; x ++ )
        for( int i = aLast[x], y = aEnd[i]; i; i = aNext[i], y = aEnd[i] )
            if( id[x] != id[y] ){
                add_edge( id[x], id[y], Size[id[y]] );
                add_redge( id[y], id[x], Size[id[x]] );
                v.push_back( make_pair( id[x], id[y] ) );
            }
    SPFA( id[1] );
    rSPFA( id[1] );


    int ans = 0;
    for( int i = 1; i <= scc; i ++ )
        if( dis[i] == -1 && rdis[i] == -1 )
            ans = Max( ans, dis[i] + rdis[i] );
    int xx = v.size();
    for( int i = 0; i < xx; i ++ ){
        a = v[i].first; b = v[i].second;
        if( dis[a] != -1 && rdis[b] != -1 )
            ans = Max( ans, dis[a] + rdis[b] + Size[id[1]] );
        if( rdis[a] != -1 && dis[b] != -1 )
            ans = Max( ans, rdis[a] + dis[b] + Size[id[1]] );
    }
    cout << ans;
    return 0;
}
  • T3

    暑期期間,何老闆閒來無事,於是買了輛摩托車,簽約某團外賣,跑起來送外賣的業務。
    何老闆負責的區域裏有n個住宅小區(編號1到n),小區間通過m條雙向道路相連,兩個小區間最多隻有一條道路相連,也不存在某小區自己到它自己的道路。每條道路有一定的長度。
    何老闆先到1號小區的餐館去領餐,然後去k個小區送餐(編號2,3,4,…,k+1),最終到n號小區的加油站去給摩托車加油。要到k個小區去送餐,根據下單時間,公司規定了其中某些小區送餐的先後順序,比如i小區的餐必須在給j小區送餐前送到。何老闆希望在滿足公司要求的情況下,使得行走的總路程最少,請你幫他計算一下。
    例如,下圖所示,起點爲1號終點爲8號小區。期間要給2、3、4、5號小區送餐。公司規定,給2號小區送餐後才能給3號小區送餐,給3號小區送餐後才能給4、5號小區送餐。最短的行程方案是1—>2—>4—>3—>4—>5—>8,總路程爲19。
    3761

    注意,可以先經過某些後送餐的小區而不停下來給它們送餐。假如,要送4號小區後才能給3號小區送餐,何老闆可以從2號經過3號到達4號小區,中間雖然經過了3號小區,但他沒有停下來,這樣就不違法公司的規定了。

輸入格式

第一行,3個空格間隔的整數n,m,k

接下來m行,每行三個整數x,y,z表示小區x也小區y間有一條長度爲z的道路(1<=x,y<=n 1<=z<=1000)

接下來一行,一個整數t,表示公司有t條要求(0<=t<=k*(k-1)/2)

接下來t行,每行兩個整數x和y,表示給x小區送餐後才能給y號小區送餐
(2<=x,y<=k+1 x!=y)

輸出格式

一行,一個整數,表示所求最短總路程。

樣例輸入 1

8 15 4
1 2 3
1 3 4
1 4 4
1 6 2
1 7 3
2 3 6
2 4 2
2 5 2
3 4 3
3 6 3
3 8 6
4 5 2
4 8 6
5 7 4
5 8 6
3
2 3
3 4
3 5

樣例輸出 1

19

樣例輸入 2

8 7 6
1 2 10
2 3 15
3 4 1
4 5 12
5 6 13
6 7 123
7 8 10
5
7 2
2 6
6 3
3 5
5 4

樣例輸出 2

588

提示

對於100%的數據 2<=N<=20000, 1<=M<=200000, 0<=K<=20

講道理學過的最短路算法無非就兩種,看到這還是個疑似稠密圖(???)所以果斷dijkstra+heap,暴力n2log2n 不可取,而且壓根就沒法處理前置條件的要求。
但是那個K(〜 ̄△ ̄)〜,所以就能想到狀壓dp啦。
所以其實是用dijkstra+heap預處理dis[][]的信息,我們只關心k+1以內的數;然後枚舉狀態,滿足前置要求的時候就可以更新了。

#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <queue>
#include <cstring>
using namespace std;
inline int R(){
    char t=getchar();int o=0;bool F=0;
    while(t<48||t>57)F|=t==45,t=getchar();
    for(;t<58&&t>47;t=getchar())o=(o<<1)+(o<<3)+t-48;
    return F?-o:o;
}
inline int Min(int x,int y){
    int m=(x-y)>>31;
    return x&m|y&~m;
}
const int N = 20005, M = 400005;
int n, m, k, t;
int cnt_edge = 1, Last[N], End[M], Len[M], Next[M];
int D[25][25], f[1048576][25], dis[N];
int bin[24], con[25];
struct node{
    int x, v;
    node(){};
    node(int a,int b){x=a;v=b;}
    bool operator < ( const node &p ) const {
        return v > p.v;
    }
};
priority_queue<node> q;
inline void add_edge( int a, int b, int c ){
    Len[++cnt_edge] = c;
    End[cnt_edge] = b;
    Next[cnt_edge] = Last[a];
    Last[a] = cnt_edge;
}

void dijkstra( int s ){
    for( int i = 1; i <= n; i ++ )
        dis[i] = 0x3fffffff;
    dis[s] = 0;
    q.push( node( s, 0 ) );
    node tmp;
    while( ! q.empty() ){
        tmp = q.top(); q.pop();
        if( dis[tmp.x] != tmp.v ) continue;
        for( int i = Last[tmp.x], y = End[i]; i; i = Next[i], y = End[i] )
            if( dis[y] > tmp.v + Len[i] ){
                dis[y] = tmp.v + Len[i];
                q.push( node( y, dis[y] ) );
            }
    }
    for( int i = 1; i <= k + 1; i ++ )
        D[s][i] = dis[i];
    D[s][0] = dis[n];
}

void w(){
    int tmp = k + 1, tot = bin[k] - 1;
    for( int j = 0; j <= tot; j ++ )
        for( int i = 1; i <= tmp; i ++ )
            f[j][i] = 0x3fffffff;
    f[0][1] = 0;
    for( int j = 0; j <= tot; j ++ )
        for( int i = 1; i <= tmp; i ++ )
            if( f[j][i] != 0x3fffffff )
                for( int p = 2, s = j | bin[p-2]; p <= tmp; p ++, s = j | bin[p-2] )
                    if( ( j & con[p] ) == con[p] && f[s][p] > f[j][i] + D[i][p] )
                            f[s][p] = f[j][i] + D[i][p];
}



int main()
{
//  freopen("atr17.in","r",stdin);
    scanf("%d%d%d",&n,&m,&k);
    bin[0] = 1;
    for( int i = 1; i <= 20; i ++ ) bin[i] = bin[i-1] << 1;
    int a, b, c, tot = bin[k] - 1;
    for( int i = 1; i <= m; i ++ ){
        scanf("%d%d%d",&a,&b,&c);
        add_edge( a, b, c );
        add_edge( b, a, c );
    }
//  cout << "!"<<endl;  
    int tmp = k + 1, s, ans = 0x7fffffff;
    for( int i = 1; i <= tmp; i ++ )
        dijkstra( i );



    scanf("%d",&t);
    for( int i = 1; i <= t; i ++ ){
        scanf("%d%d",&a,&b);
        con[b] += bin[a-2];
    }
    w();

    for( int i = 1; i <= tmp; i ++ )
        if( f[tot][i] != 0x3fffffff )
            ans = Min( ans, f[tot][i] + D[i][0] );
    cout << ans;
    return 0;
}

每次考完都覺得自己是個ZZ( ̄ε(# ̄) Σ

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章