日常失了智的比赛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( ̄ε(# ̄) Σ

发布了11 篇原创文章 · 获赞 5 · 访问量 3381

猜你喜欢

转载自blog.csdn.net/qq_24855707/article/details/77927688