1.Dijkstra:单源最短路

bool vis[MAXN];
int d[MAXN];
priority_queue<pair<int,int>, vector<pair<int,int> >, greater<pair<int,int> > > Q;
void Dijestra(int begin){
    memset(d, 127, sizeof(d));
    d[begin] = 0;
    Q.push(make_pair(0, begin));
    while(!Q.empty()){
        int x = Q.top().second;
        int cost = Q.top().first;
        Q.pop();
        if(vis[x]) continue;
        vis[x] = true;
        for(int l=head[x]; l; l=next[l]){
            int y = last[l];
            if(!vis[y] && d[y] > val[l] + cost)
                d[y] = val[l] + cost, Q.push(make_pair(d[y], y));
        }
    }
}

时间复杂度:$O((n+m)log(n+m))$

2.SPFA:单源最短路,判断负环

int d[MAXN];
bool inQ[MAXN];
queue<int> Q;
void SPFA(int begin){
    memset(d, 127, sizeof(d));
    Q.push(begin), inQ[begin] = true, d[begin] = 0;
    while(!Q.empty()){
        int x = Q.front();
        inQ[x] = false, Q.pop();
        for(int l=head[x]; l; l=next[l]){
            int y = last[l];
            if(d[y] > d[x] + val[l]){
                d[y] = d[x] + val[l];
                if(!inQ[y]) Q.push(y), inQ[y] = true;
            }
        }
    }
}

时间复杂度:随机数据$O(km)$,最坏情况$O(nm)$

3.Floyd:多源最短路,矩阵乘法

for(int k=1; k<=n; k++)
    for(int i=1; i<=n; i++)
        for(int j=1; j<=n; j++)
            dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);

时间复杂度:$O(n^3)$

经典例题:

1.在Dp转移决策需要预处理时,可将一个Dp看作图中一个点,决策代价看作边权,起始状态看作出发点,最终状态看作终点。

例题:Online 1503 BZOJ2259 新型计算机

Dp转移方程十分容易推:$Dp[i] = min(Dp[i+a[i]+1], Dp[j]+abs(a[i]-(i-j-1)))$,然而转移却需要$O(n)$的时间,面对$n<=10^6$的数据是过不了的(如果用线段树可以$O(logn)$转移)

看到这里,我想到用图论,在$i$和$i+a[i]+1$之间连一条长度为$0$的边,从$i+a[i]+1$开始向两边的点连长度为$1$的边(如果已经连过就跳过),平均连边时间复杂度为$O(n)$,最多不超过4n条点,起点$0$,终点为$n+1$,求最短路即可。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
using namespace std;

long long read(){
    long long ans = 0, f = 1;
    char ch = getchar();
    while(!isdigit(ch))
        f *= (ch == '-') ? -1 : 1, ch = getchar();
    do ans = (ans << 1) + (ans << 3) + (ch ^ 48), ch = getchar();
    while(isdigit(ch));
    return ans * f;
}

const int MAXN = 1000003, MAXM = 4 * MAXN;
int head[MAXN], next[MAXM], last[MAXM], val[MAXM], lineNum = 0;
void add(int x,int y,int v){
    lineNum++, next[lineNum] = head[x], head[x] = lineNum, last[lineNum] = y, val[lineNum] = v;
}

int a[MAXN];
bool conneL[MAXN], conneR[MAXN], inQ[MAXN];

bool vis[MAXN];
int d[MAXN];
priority_queue<pair<int,int>, vector<pair<int,int> >, greater<pair<int,int> > > Q;
void Dijestra(int begin){
    memset(d, 127, sizeof(d));
    d[begin] = 0;
    Q.push(make_pair(0, begin));
    while(!Q.empty()){
        int x = Q.top().second;
        int cost = Q.top().first;
        Q.pop();
        if(vis[x]) continue;
        vis[x] = true;
        for(int l=head[x]; l; l=next[l]){
            int y = last[l];
            if(!vis[y] && d[y] > val[l] + cost)
                d[y] = val[l] + cost, Q.push(make_pair(d[y], y));
        }
    }
}

int main(){
    int n = read();
    for(int i=1; i<=n; i++)
        a[i] = read();
    for(int i=1; i<=n; i++){
        if(i+a[i] <= n)
            add(i, i+a[i]+1, 0);
        if(i+a[i] > n){
            add(i, n+1, a[i]+i-n);
            continue;
        }
        for(int j=a[i]+i+1; j>i && !conneL[j]; j--)
            add(j, j-1, 1), conneL[j] = true;
        for(int j=a[i]+i+1; j<=n && !conneR[j]; j++)
            add(j, j+1, 1), conneR[j] = true;
    }
    Dijestra(1);
    printf("%d", d[n+1]);
    return 0;
}
View Code

2.Dp转移决策无法确定是否计算时

例题:Online 1292 NOIP2018模拟 过河

这道题的关键转移的决策不好推,一般的Dp都是顺序递推,而像这道题的决策却有很多情况,考虑用图论,只需要保证图中存在一条路径是正确答案即可。

首先从假想的原点连到可以到岸边的板子上,对终点同样考虑,之后考虑版子之间的连接,最后对于一个板子,可以花费一些代价使自己变得更大,同样进行连边(重要),因为你不可能两次经过同一个板子,所以花费代价再跳到其他板子是正确的决策。

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

const int MAXN = 255,MAXPOINT = 255*255,MAXLINE = 255*255*255*2;

struct point
{
    long long X,Y;
};
struct code
{
    long long R,V;
};
bool cmp(code a1,code a2)
{
    if(a1.R == a2.R)
        return a1.V < a2.V;
    else
        return a1.R > a2.R;
}
int head[MAXPOINT], next[MAXLINE], value[MAXLINE], last[MAXLINE], LineNum = 0;
int PointNum, CodeNum, Breath;
long long Cost[MAXPOINT];
point P[MAXN];
code C[MAXN];

void add(int x,int y,int v)
{
    LineNum++, value[LineNum] = v,last[LineNum] = y,next[LineNum] = head[x], head[x] = LineNum;
}

void init()
{
    memset(head, 0, sizeof(head));
    LineNum = 0;
    memset(Cost, 127, sizeof(Cost));
    scanf("%d%d%d",&PointNum,&CodeNum,&Breath);
    for(int i=1; i<=PointNum; i++)
        scanf("%lld%lld",&P[i].X,&P[i].Y);
    for(int i=1; i<=CodeNum; i++)
        scanf("%lld%lld",&C[i].R,&C[i].V);
    return;
}

void preduct()
{
    sort(C+1, C+1+CodeNum, cmp);
    bool cut[CodeNum+1];
    long long cost = C[1].V;
    for(int i=2; i<=CodeNum; i++)
    {
        if(C[i].R == C[i-1].R)
            cut[i] = true;
        else if(C[i].V >= cost)
            cut[i] = true;
        else
            cost = C[i].V, cut[i] = false;
    }
    int p1 = 1, p2 = 1;
    while(p2 <= CodeNum)
    {
        if(p1 != p2)
            C[p1] = C[p2];
        p1++, p2++;
        while(p2 <= CodeNum && cut[p2])
            p2++;
    }
    CodeNum = p1-1;
}

void addLine()
{
    for(int i=1; i<=PointNum; i++)
    {
        for(int j=1; j<=PointNum; j++)
        {
            if(i == j)
                continue;
            int r = CodeNum;
            long long d = (P[i].Y-P[j].Y)*(P[i].Y-P[j].Y) + (P[i].X-P[j].X)*(P[i].X-P[j].X);
            for(int k=1; k<=CodeNum; k++)
            {
                while(r>0 && (C[r].R+C[k].R)*(C[r].R+C[k].R) < d)
                    r--;
                if(r != 0)
                    add((i-1)*CodeNum+k, (j-1)*CodeNum+r, C[r].V);
            }
        }
    }
    for(int i=1; i<=PointNum; i++)
        for(int j=CodeNum-1; j>=1; j--)
            add((i-1)*CodeNum+j+1, (i-1)*CodeNum+j, C[j].V-C[j+1].V);
    for(int i=1; i<=PointNum; i++)
    {
        for(int j=1; j<=CodeNum; j++)
        {
            if(C[j].R < P[i].Y)
                break;
            add(0, (i-1)*CodeNum+j, C[j].V);
        }
    }
    for(int i=1; i<=PointNum; i++)
    {
        for(int j=1; j<=CodeNum; j++)
        {
            if(C[j].R < Breath - P[i].Y)
                break;
            add((i-1)*CodeNum+j, CodeNum*PointNum+1, 0);
        }
    }
}

void SPFA()
{
    queue<int> q;
    q.push(0);
    Cost[0] = 0;
    while(!q.empty())
    {
        int t = q.front();
        q.pop();
        for(int i = head[t]; i; i = next[i])
        {
            if(Cost[last[i]] > Cost[t] + value[i])
            {
                Cost[last[i]] = Cost[t] + value[i];
                q.push(last[i]);
            }
        }
    }
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        init();
        preduct();
        addLine();
        SPFA();
        if(Cost[CodeNum * PointNum + 1] == 9187201950435737471)
            printf("impossible\n");
        else
            printf("%lld\n",Cost[CodeNum * PointNum + 1]);
    }
    return 0;
}
View Code

3.Floyd与矩阵乘法的结合

例题:Online 1119 奶牛接力

看着Floyd的代码,是否觉得有些熟悉,这不就是矩阵乘法的翻版嘛。其实从意义上来说,如果结果单独存在一个数组中,就有了新的意义——$Dp[i][j]$表示从$i$到$j$经历$k$个点后的图的连通性,而且从意义上来看,矩阵乘法满足结合律(当然不一定满足交换率),所以可以使用快速幂。

#include <iostream>
#include <cstdio>
#include <map>
#include <cstring>
#include <climits>
using namespace std;

long long read()
{
    long long ans = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
        f *= (ch == '-') ? -1 : 1, ch = getchar();
    do ans = (ans << 1) + (ans << 3) + (ch ^ 48), ch = getchar();
    while(ch >= '0' && ch <= '9');
    return ans * f;
}

class Matrix
{
    public:
    long long ** value;
    int size;
    void reset(int size)
    {
        this->size = size;
        value = new long long * [size];
        for(int i=0; i<size; i++)
            value[i] = new long long [size];
    }
    Matrix(int size)
    {
        reset(size);
    }
    void operator = (const int * a)
    {
        int * index = (int *)a;
        for(int i=0; i<size; i++)
            for(int j=0; j<size; j++)
                value[i][j] = *index, index++;
    }
    void operator *= (Matrix &other)
    {
        long long ans[size][size];
        for(int i=0; i<size; i++){
            for(int j=0; j<size; j++){
                ans[i][j] = LLONG_MAX >> 1;
                for(int k=0; k<size; k++)
                    ans[i][j] = min(ans[i][j], value[i][k] + other.value[k][j]);
            }
        }
        for(int i=0; i<size; i++)
            for(int j=0; j<size; j++)
                value[i][j] = ans[i][j];
    }
    void operator = (Matrix &other)
    {
        for(int i=0; i<size; i++)
            for(int j=0; j<size; j++)
                value[i][j] = other.value[i][j];
    }
    void operator ^= (long long n)
    {
        Matrix unit(size);
        unit = (*this);
        bool flag = false;
        while(n){
            if(n & 1){
                if(!flag)
                    flag = true, (*this) = unit;
                else
                    (*this) *= unit;
            }
            unit *= unit;
            n >>= 1;
        }
    }
};

const int MAXN = 101;
int l[MAXN*2], a[MAXN*2], b[MAXN*2];

int main()
{
    int n = read(), T = read(), begin = read(), end = read();
    int pointNum = 0;
    map<int,int> m;
    for(int i=1; i<=T; i++)
    {
        l[i] = read(), a[i] = read(), b[i] = read();
        if(m.find(a[i]) == m.end())
            m[a[i]] = pointNum++;
        if(m.find(b[i]) == m.end())
            m[b[i]] = pointNum++;
    }
    int a_unit[pointNum][pointNum];
    memset(a_unit, 127, sizeof(a_unit));
    for(int i=1; i<=T; i++)
        a_unit[m[a[i]]][m[b[i]]] = a_unit[m[b[i]]][m[a[i]]] = l[i];
    Matrix unit(pointNum);
    unit = a_unit[0];
    unit ^= n;
    printf("%d",unit.value[m[begin]][m[end]]);
    return 0;
}
View Code

同样还有一道例题:Online 1118 沼泽鳄鱼 也是用同样的思路

#include <iostream>
#include <cstdio>
#include <cstring>
#include <climits>
using namespace std;

const int MOD = 10000;
long long read(){
    long long ans = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
        f *= (ch == '-') ? -1 : 1, ch = getchar();
    do ans = ((ans << 1) + (ans << 3) + (ch ^ 48)), ch = getchar();
    while(ch >= '0' && ch <= '9');
    return ans * f;
}

const int MAXN = 50;
struct Matrix{
    int size;
    long long value[MAXN][MAXN];
    Matrix(int SIZE = 0){
        size = SIZE;
        memset(value, 0, sizeof(value));
    }
    void normal(){
        for(int i=0; i<size; i++)
            for(int j=0; j<size; j++)
                value[i][j] = (i == j) ? 1 : 0;
    }
    void operator = (long long * a){
        long long * index = a;
        for(int i=0; i<size; i++)
            for(int j=0; j<size; j++)
                value[i][j] = *index, index++;
    }
    void operator *= (Matrix &other){
        long long ans[size][size];
        memset(ans, 0, sizeof(ans));
        for(int i=0; i<size; i++)
            for(int j=0; j<size; j++)
                for(int k=0; k<size; k++)
                    ans[i][j] = (ans[i][j] + value[i][k] * other.value[k][j]) % MOD;
        for(int i=0; i<size; i++)
            for(int j=0; j<size; j++)
                value[i][j] = ans[i][j];
    }
    void operator = (Matrix &other){
        size = other.size;
        memcpy(value, other.value, sizeof(value));
    }
    void operator ^= (long long n){
        Matrix unit = (*this);
        this->normal();
        while(n){
            if(n & 1)
                (*this) *= unit;
            unit *= unit;
            n >>= 1;
        }
    }
    void print()
    {
        for(int i=0; i<size; i++){
            for(int j=0; j<size; j++)
                printf("%d ",value[i][j]);
            putchar('\n');
        }
    }
};

int main(){
    int N = read(), M = read(), Start = read(), End = read(), K = read();
    Matrix g[12];
    for(int i=0; i<12; i++){
        g[i].size = N;
        memset(g[i].value, 0, sizeof(g[i].value));
    }
    for(int i=1; i<=M; i++){
        int x = read(), y = read();
        for(int j=0; j<12; j++){
            g[j].value[x][y] = g[j].value[y][x] = 1;
        }
    }
    int NFish = read();
    for(int i=1; i<=NFish; i++){
        int T = read();
        int w[T];
        for(int j=0; j<T; j++)
            w[j] = read();
        for(int j=1; j<=12; j++)
            for(int k=0; k<N; k++)
                g[j-1].value[k][w[j%T]] = 0;
    }
    Matrix ans(N);
    ans.normal();
    for(int i=0; i<12; i++)
        ans *= g[i];
    ans ^= (K / 12);
    for(int i=1; i<=K%12; i++)
        ans *= g[i-1];
    printf("%lld",ans.value[Start][End]);
    return 0;
}
View Code
posted on 2020-01-19 15:19  PHDHD  阅读(199)  评论(0编辑  收藏  举报