主要内容:

1.状态压缩DP
2.DP基础优化
3.DP单调性优化
4.拓扑排序
5.最小生成树
6.最短路及差分约束

状态压缩DP

 

 

定义:

状态压缩DP,顾名思义就是将状态压缩起来.

模板:

#include<bits/stdc++.h> 
using namespace std;
typedef long long LL;
const int N = 12, M = 1 << N;
int n, m;
LL f[N][M];
vector<int> state[M];
bool st[M];
int main() {
    while (cin >> n >> m, n || m) {
        for (int i = 0; i < 1 << n; i ++ ) {
            int cnt = 0;
            bool is_valid = true;
            for (int j = 0; j < n; j ++ )
                if (i >> j & 1) {
                    if (cnt & 1) {
                        is_valid = false;
                        break;
                    }
                    cnt = 0;
                } else cnt ++ ;
            if (cnt & 1) is_valid = false;
            st[i] = is_valid;
        }
        for (int i = 0; i < 1 << n; i ++ ) {
            state[i].clear();
            for (int j = 0; j < 1 << n; j ++ )
                if ((i & j) == 0 && st[i | j])
                    state[i].push_back(j);
        }
        memset(f, 0, sizeof f);
        f[0][0] = 1;
        for (int i = 1; i <= m; i ++ )
            for (int j = 0; j < 1 << n; j ++ )
                for (auto k : state[j])
                    f[i][j] += f[i - 1][k];
        cout << f[m][0] << endl;
    }
    return 0;
}

DP基础优化

LIS的O(nlgn)算法

模板:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=4e4+1;
int a[MAXN],b[MAXN],m,n,len;
int bs(int left,int right,int a[],int val) { 
    int r=right,l=left;
    while(l<r) {
        int mid=r+l>>1;
        if(val<a[mid])r=mid;
        else l=mid+1;
    }
    return l;
}
int main() {
    scanf("%d",&m);
    for(int k=0; k<m; k++) {
        scanf("%d",&n);
        for(int i=1; i<=n; i++)scanf("%d",&a[i]);
        b[1]=a[1];
        len=1;
        for(int i=2; i<=n; i++) {
            if(a[i]>b[len]) {
                len++;
                b[len]=a[i];
            } else {
                int index = bs(1,len,b,a[i]);
                b[index]=a[i];
            }
        }
        printf("%d\n",len);
    }
    return 0;
}

DP的滚动数组优化

模板:

#include<bits/stdc++.h>
using namespace std;
const int maxn=510,mod=1e9+7;
char mp[510][510];
int dp[2][510][510];
int n,m;
int nx[]= {0,0,-1,-1};
int ny[]= {0,1,1,0};
int main() {
    cin>>n>>m;
    for(int i=1; i<=n; i++) cin>>mp[i]+1;
    int now=0;
    if(mp[1][1]==mp[n][m]) dp[now][1][n]=1;
    for(int len=1; len<=(n+m-2)/2; len++) {
        now^=1;
        for(int i=1; i<=n; i++)
            for(int j=1; j<=n; j++)
                dp[now][i][j]=0;
        for(int x1=1; x1-1<=len&&x1<=n; x1++) {
            for(int x2=n; n-x2<=len&&x2>=1; x2--) {
                int y1=1+len-(x1-1),y2=m-len+(n-x2);
                if(mp[x1][y1]==mp[x2][y2]) {
                    for(int k=0; k<4; k++) {
                        dp[now][x1][x2]=(dp[now][x1][x2]+dp[now^1][x1+nx[k]][x2+ny[k]])%mod;
                    }
                }
            }
        }
    }
    int res=0;
    for(int i=1; i<=n; i++) res=(res+dp[now][i][i])%mod;
    if((n+m)%2) {
        for(int i=1; i<n; i++) res=(res+dp[now][i][i+1])%mod;
    }
    cout<<res<<endl;
    return 0;
}

前缀不会!!!

DP单调性优化

模板:

#include<bits/stdc++.h>
using namespace std;
const int N = 300010, INF = 1e9;
int n, m;
int s[N];
int q[N];
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &s[i]), s[i] += s[i - 1];
    int res = -INF;
    int hh = 0, tt = 0;
    for (int i = 1; i <= n; i ++ ) {
        if (q[hh] < i - m) hh ++ ;
        res = max(res, s[i] - s[q[hh]]);
        while (hh <= tt && s[q[tt]] >= s[i]) tt -- ;
        q[ ++ tt] = i;
    }
    printf("%d\n", res);
    return 0;
}

DP斜率优化

模板:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 5010;
int n, s;
int sc[N], st[N];
LL f[N];
int main() {
    scanf("%d%d", &n, &s);
    for (int i = 1; i <= n; i ++ ) {
        scanf("%d%d", &st[i], &sc[i]);
        st[i] += st[i - 1];
        sc[i] += sc[i - 1];
    }
    memset(f, 0x3f, sizeof f);
    f[0] = 0;
    for (int i = 1; i <= n; i ++ )
        for (int j = 0; j < i; j ++ )
            f[i] = min(f[i], f[j] + (sc[i] - sc[j]) * (LL)st[i] + (LL)s * (sc[n] - sc[j]));
    printf("%lld\n", f[n]);
    return 0;
}

拓扑排序

模板:

#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, m;
int h[N], e[N], ne[N], idx;
int d[N];
int q[N];
void add(int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
bool topsort() {
    int hh = 0, tt = -1;
    for (int i = 1; i <= n; i ++ )
        if (!d[i])
            q[ ++ tt] = i;
    while (hh <= tt) {
        int t = q[hh ++ ];
        for (int i = h[t]; i != -1; i = ne[i]) {
            int j = e[i];
            if (-- d[j] == 0)
                q[ ++ tt] = j;
        }
    }
    return tt == n - 1;
}
int main() {
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof h);
    for (int i = 0; i < m; i ++ ) {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b);
        d[b] ++ ;
    }
    if (!topsort()) puts("-1");
    else {
        for (int i = 0; i < n; i ++ ) printf("%d ", q[i]);
        puts("");
    }
    return 0;
}

最小生成树

Kruskal算法求最小生成树

code

#include<bits/stdc++.h>
using namespace std;
const int N = 100010, M = 200010, INF = 0x3f3f3f3f;
int n, m;
int p[N];
struct Edge {
    int a, b, w;
    bool operator< (const Edge &W)const {
        return w < W.w;
    }
} edges[M];
int find(int x) {
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}
int kruskal() {
    sort(edges, edges + m);
    for (int i = 1; i <= n; i ++ ) p[i] = i;    // 初始化并查集
    int res = 0, cnt = 0;
    for (int i = 0; i < m; i ++ ) {
        int a = edges[i].a, b = edges[i].b, w = edges[i].w;
        a = find(a), b = find(b);
        if (a != b) {
            p[a] = b;
            res += w;
            cnt ++ ;
        }
    }
    if (cnt < n - 1) return INF;
    return res;
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 0; i < m; i ++ ) {
        int a, b, w;
        scanf("%d%d%d", &a, &b, &w);
        edges[i] = {a, b, w};
    }
    int t = kruskal();
    if (t == INF) puts("impossible");
    else printf("%d\n", t);
    return 0;
}

最短路及差分约束

提到最短路,我们先来回顾一下之前水过的最短路模板

 Dijkstra

#include<bits/stdc++.h>
using namespace std;
const int N = 510;
int n, m;
int g[N][N];
int dist[N];
bool st[N];
int dijkstra() {
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    for (int i = 0; i < n - 1; i ++ ) {
        int t = -1;
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;
        for (int j = 1; j <= n; j ++ )
            dist[j] = min(dist[j], dist[t] + g[t][j]);
        st[t] = true;
    }
    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}
int main() {
    scanf("%d%d", &n, &m);
    memset(g, 0x3f, sizeof g);
    while (m -- ) {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        g[a][b] = min(g[a][b], c);
    }
    printf("%d\n", dijkstra());
    return 0;
}

Bellman-ford

#include<bits/stdc++.h>
using namespace std;
const int N = 510, M = 10010;
struct Edge {
    int a, b, c;
} edges[M];
int n, m, k;
int dist[N];
int last[N];
void bellman_ford() {
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    for (int i = 0; i < k; i ++ ) {
        memcpy(last, dist, sizeof dist);
        for (int j = 0; j < m; j ++ ) {
            auto e = edges[j];
            dist[e.b] = min(dist[e.b], last[e.a] + e.c);
        }
    }
}
int main() {
    scanf("%d%d%d", &n, &m, &k);
    for (int i = 0; i < m; i ++ ) {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        edges[i] = {a, b, c};
    }
    bellman_ford();
    if (dist[n] > 0x3f3f3f3f / 2) puts("impossible");
    else printf("%d\n", dist[n]);
    return 0;
}

Spfa

#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, m;
int h[N], w[N], e[N], ne[N], idx;
int dist[N];
bool st[N];
void add(int a, int b, int c) {
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
int spfa() {
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    queue<int> q;
    q.push(1);
    st[1] = true;
    while (q.size()) {
        int t = q.front();
        q.pop();
        st[t] = false;
        for (int i = h[t]; i != -1; i = ne[i]) {
            int j = e[i];
            if (dist[j] > dist[t] + w[i]) {
                dist[j] = dist[t] + w[i];
                if (!st[j]) {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
    return dist[n];
}
int main() {
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof h);
    while (m -- ) {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
    }
    int t = spfa();
    if (t == 0x3f3f3f3f) puts("impossible");
    else printf("%d\n", t);
    return 0;
}

Floyd

#include<bits/stdc++.h>
using namespace std;
const int N = 210, INF = 1e9;
int n, m, Q;
int d[N][N];
void floyd() {
    for (int k = 1; k <= n; k ++ )
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= n; j ++ )
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
int main() {
    scanf("%d%d%d", &n, &m, &Q);
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            if (i == j) d[i][j] = 0;
            else d[i][j] = INF;
    while (m -- ) {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        d[a][b] = min(d[a][b], c);
    }
    floyd();
    while (Q -- ) {
        int a, b;
        scanf("%d%d", &a, &b);
        int t = d[a][b];
        if (t > INF / 2) puts("impossible");
        else printf("%d\n", t);
    }
    return 0;
}

tips:后两种非必要情况下不要用,不然有可能这样

 

 这样

 

 或是这样

 

 不要问我是怎么知道的……

最后剩下差分约束,来道例题看一看

幼儿园里有 NN 个小朋友,老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果。

但是小朋友们也有嫉妒心,总是会提出一些要求,比如小明不希望小红分到的糖果比他的多,于是在分配糖果的时候, 老师需要满足小朋友们的 KK 个要求。

幼儿园的糖果总是有限的,老师想知道他至少需要准备多少个糖果,才能使得每个小朋友都能够分到糖果,并且满足小朋友们所有的要求。

输入格式

输入的第一行是两个整数 N,KN,K。

接下来 KK 行,表示分配糖果时需要满足的关系,每行 33 个数字 X,A,BX,A,B。

  • 如果 X=1X=1.表示第 AA 个小朋友分到的糖果必须和第 BB 个小朋友分到的糖果一样多。
  • 如果 X=2X=2,表示第 AA 个小朋友分到的糖果必须少于第 BB 个小朋友分到的糖果。
  • 如果 X=3X=3,表示第 AA 个小朋友分到的糖果必须不少于第 BB 个小朋友分到的糖果。
  • 如果 X=4X=4,表示第 AA 个小朋友分到的糖果必须多于第 BB 个小朋友分到的糖果。
  • 如果 X=5X=5,表示第 AA 个小朋友分到的糖果必须不多于第 BB 个小朋友分到的糖果。

小朋友编号从 11 到 NN。

输出格式

输出一行,表示老师至少需要准备的糖果数,如果不能满足小朋友们的所有要求,就输出 1−1。

数据范围

1N<1051≤N<105,
1K1051≤K≤105,
1X51≤X≤5,
1A,BN1≤A,B≤N,
输入数据完全随机。

输入样例:

5 7
1 1 2
2 3 2
4 4 1
3 4 5
5 4 5
2 3 5
4 5 1

输出样例:

11

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 100010, M = 300010;
int n, m;
int h[N], e[M], w[M], ne[M], idx;
LL dist[N];
int q[N], cnt[N];
bool st[N];
void add(int a, int b, int c) {
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
bool spfa() {
    int hh = 0, tt = 1;
    memset(dist, -0x3f, sizeof dist);
    dist[0] = 0;
    q[0] = 0;
    st[0] = true;
    while (hh != tt) {
        int t = q[ -- tt];
        st[t] = false;
        for (int i = h[t]; ~i; i = ne[i]) {
            int j = e[i];
            if (dist[j] < dist[t] + w[i]) {
                dist[j] = dist[t] + w[i];
                cnt[j] = cnt[t] + 1;
                if (cnt[j] >= n + 1) return false;
                if (!st[j]) {
                    q[tt ++ ] = j;
                    st[j] = true;
                }
            }
        }
    }
    return true;
}
int main() {
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof h);
    while (m -- ) {
        int x, a, b;
        scanf("%d%d%d", &x, &a, &b);
        if (x == 1) add(b, a, 0), add(a, b, 0);
        else if (x == 2) add(a, b, 1);
        else if (x == 3) add(b, a, 0);
        else if (x == 4) add(b, a, 1);
        else add(a, b, 0);
    }
    for (int i = 1; i <= n; i ++ ) add(0, i, 1);
    if (!spfa()) puts("-1");
    else {
        LL res = 0;
        for (int i = 1; i <= n; i ++ ) res += dist[i];
        printf("%lld\n", res);
    }
    return 0;
}

溜了溜了……