1.背包

https://blog.csdn.net/yandaoqiusheng/article/details/84782655

这个博客基本总结的比较全了。

2.斜率优化

HDU 3507

暴力 f[i]=min{f[j]+s[i]^2+s[j]^2-s[i]*s[j]+W}

f[i]=f[j]+s[i]^2+s[j]^2-s[i]*s[j]+W

假设j>k 当j比k优时 有f[j]+s[i]^2+s[j]^2-s[i]s[j]+W <= f[k]+s[i]^2+s[k]^2-s[i]s[k]+W

化简有 ( (f[j]+s[j]^2)-(f[k]+s[k]^2) )/(s[j]-s[k])<=2*s[i]

令 F[x]=f[x]+s[x]^2 K[k,j]表示k->j构成的斜率 搞成点 (F[x],s[x])

则最后可以转移的集合一定是一个下凸包

反证:如果存在一个上凸包 k j i 若K[k,j]>K[j,i]>2*s[i] 则j比i好 k比j好 其他情况同理

而且这个凸包有单调性 也就是在所有 K<=2*s[i]的点构成的直线中 最右边的点是最优的

可以二分找到最优值

#include<bits/stdc++.h>

#define ll long long
#define maxn 500010
using namespace std;
ll n, m, q[maxn], hea, tai;
ll f[maxn], s[maxn];

ll F(ll x) {
    return f[x] + s[x] * s[x];
}

ll cal(ll i, ll j) {
    return f[j] + (s[i] - s[j]) * (s[i] - s[j]) + m;
}

bool K_big(ll i, ll x) {
    return F(q[i]) - F(q[i - 1]) <= 2 * s[x] * (s[q[i]] - s[q[i - 1]]);
}

bool K_bg(ll a1, ll b1, ll a2, ll b2) {
    return (F(a1) - F(b1)) * (s[a2] - s[b2]) >= (F(a2) - F(b2)) * (s[a1] - s[b1]);
}

int main() {
    while (~scanf("%lld%lld", &n, &m)) {
        hea = tai = 0;
        for (ll i = 1; i <= n; i++) {
            scanf("%lld", &s[i]);
            s[i] = s[i - 1] + s[i];
        }
        for (ll i = 1; i <= n; i++) {
            f[i] = cal(i, 0);
            while (tai - hea > 1 && K_big(hea + 2, i))hea++;
            f[i] = min(f[i], cal(i, q[hea + 1]));
            while (tai - hea > 1 && K_bg(q[tai], q[tai - 1], i, q[tai]))tai--;
            q[++tai] = i;
        }
        printf("%lld\n", f[n]);
    }
    return 0;
}

然后发现2*s[i]是单调的,也就是每次二分找到的mid也是单调的,就可以不用二分了,直接单调队列就可以O(1)的转移了。

#include<bits/stdc++.h>

#define ll long long
#define maxn 500010
using namespace std;
ll n, m, q[maxn], hea, tai;
ll f[maxn], s[maxn];

ll F(ll x) {
    return f[x] + s[x] * s[x];
}

ll cal(ll i, ll j) {
    return f[j] + (s[i] - s[j]) * (s[i] - s[j]) + m;
}

bool K_big(ll i, ll x) {
    return F(q[i]) - F(q[i - 1]) <= 2 * s[x] * (s[q[i]] - s[q[i - 1]]);
}

bool K_bg(ll a1, ll b1, ll a2, ll b2) {
    return (F(a1) - F(b1)) * (s[a2] - s[b2]) >= (F(a2) - F(b2)) * (s[a1] - s[b1]);
}

int main() {
    while (~scanf("%lld%lld", &n, &m)) {
        hea = tai = 0;
        for (ll i = 1; i <= n; i++) {
            scanf("%lld", &s[i]);
            s[i] = s[i - 1] + s[i];
        }
        for (ll i = 1; i <= n; i++) {
            f[i] = cal(i, 0);
            while (tai - hea > 1 && K_big(hea + 2, i))hea++;
            f[i] = min(f[i], cal(i, q[hea + 1]));
            while (tai - hea > 1 && K_bg(q[tai], q[tai - 1], i, q[tai]))tai--;
            q[++tai] = i;
        }
        printf("%lld\n", f[n]);
    }
    return 0;
}

暑假牛客最后一场最后一题

/*
这题比较斜率的时候如果改成不丢失精度的*好像爆longlong 需要int128
当然保留除法精度也没啥问题
*/
#include<bits/stdc++.h>

#define ll long long
#define db long double
#define maxn 5010
using namespace std;
ll n, m, f[maxn][maxn], s[maxn], ss[maxn], q[maxn], hea, tai, p;
const db One = 1.0;
struct node {
    ll h, w;
} a[maxn];

ll cmp(node a, node b) {
    return a.h > b.h;
}

ll F(ll x) {
    return f[p - 1][x] - s[x];
}

ll cal(ll j, ll k) {
    return f[p - 1][k] + s[j] - s[k] - (ss[j] - ss[k]) * a[j].h;
}

bool K_big(ll i, ll x) {
    return One * (F(q[i]) - F(q[i - 1])) / (ss[q[i]] - ss[q[i - 1]]) <= -a[x].h;
}

bool K_bg(ll a1, ll b1, ll a2, ll b2) {
    return One * (F(a1) - F(b1)) / (ss[a1] - ss[b1]) >= One * (F(a2) - F(b2)) / (ss[a2] - ss[b2]);
}

int main() {
    scanf("%lld%lld", &n, &m);
    for (ll i = 1; i <= n; i++)
        scanf("%lld%lld", &a[i].w, &a[i].h);
    sort(a + 1, a + 1 + n, cmp);
    for (ll i = 1; i <= n; i++) {
        s[i] = s[i - 1] + a[i].h * a[i].w;
        ss[i] = ss[i - 1] + a[i].w;
    }
    memset(f, 127 / 3, sizeof(f));
    f[0][0] = 0;
    for (ll i = 1; i <= m; i++) {
        p = i;
        hea = tai = 0;
        for (ll j = i; j <= n; j++) {
            f[i][j] = cal(j, i - 1);
            while (tai - hea > 1 && K_big(hea + 2, j))hea++;
            f[i][j] = min(f[i][j], cal(j, q[hea + 1]));
            while (tai - hea > 1 && K_bg(q[tai], q[tai - 1], j, q[tai - 1]))tai--;
            q[++tai] = j;
        }
    }
    printf("%lld\n", f[m][n]);
    return 0;
}

3.决策单调

首先比较经典的就是四边形不等式优化dp

证明的话,大概就是先证costok,然后dpok,然后就是决策单调。

当然了,打比赛的时候是不可能证的,就打表就完事了。

#include<bits/stdc++.h>

#define maxn 210
using namespace std;
int n, a[maxn], ss[maxn], f[maxn][maxn], g[maxn][maxn], s[maxn][maxn];

int main() {
    scanf("%d", &n);
    memset(f, 127 / 3, sizeof(f));
    memset(g, -127 / 3, sizeof(g));
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        a[i + n] = a[i];
    }
    int a1 = 1e9, a2 = 0;
    n = n * 2;
    for (int i = 1; i <= n; i++) {
        ss[i] = ss[i - 1] + a[i];
        f[i][i] = 0, g[i][i] = 0, s[i][i] = i;
    }
    for (int i = n - 1; i >= 1; i--)
        for (int j = i + 1; j <= n; j++) {
            for (int k = s[i][j - 1]; k <= s[i + 1][j]; k++)
                if (f[i][j] > f[i][k] + f[k + 1][j] + ss[j] - ss[i - 1]) {
                    f[i][j] = f[i][k] + f[k + 1][j] + ss[j] - ss[i - 1];
                    s[i][j] = k;
                }
            if (j - i + 1 == n / 2)a1 = min(a1, f[i][j]);
//            printf("%d %d %d\n",i,j,s[i][j]);

        }
    for (int i = n - 1; i >= 1; i--)
        for (int j = i + 1; j <= n; j++) {
            for (int k = i; k <j; k++)
                if (g[i][j] < g[i][k] + g[k + 1][j] + ss[j] - ss[i - 1]) {
                    g[i][j] = g[i][k] + g[k + 1][j] + ss[j] - ss[i - 1];
                    s[i][j] = k;
                }
            if (j - i + 1 == n / 2)a2 = max(a2, g[i][j]);
//            printf("%d %d %d\n",i,j,s[i][j]);
        }
    printf("%d\n%d\n", a1, a2);
    return 0;
}

前几天的训练里面有一个扔鸡蛋的dp,怎么想都不知道怎么优化,最后打表可以看出来转移的单调。

#include<bits/stdc++.h>

#define mod 100000073
#define maxn 5000010
using namespace std;
int f[maxn], a[maxn], b[maxn], s[maxn], ss[maxn];

int cal(int i, int j) {
    return max(j - 1, f[i - j]);
}

void pre(int n) {
    f[1] = a[1] = b[1] = s[0] = s[1] = ss[0] = 1, ss[1] = 2;
    for (int i = 2; i <= n; i++) {
        for (b[i] = b[i - 1]; b[i] < i && cal(i, b[i] + 1) <= cal(i, b[i]); b[i]++);
        f[i] = cal(i, b[i]) + 1;
        for (a[i] = a[i - 1]; a[i] < i && cal(i, a[i]) >= f[i]; a[i]++);
        for (; a[i] > 1 && cal(i, a[i] - 1) < f[i]; a[i]--);
        s[i] = (ss[i - a[i]] - ss[i - b[i] - 1] + mod) % mod;
        ss[i] = (ss[i - 1] + s[i]) % mod;
    }
}

int main() {
    pre(5000000);
    int x, y;
    while (~scanf("%d%d", &x, &y))
        printf("%d %d\n", f[y - x + 1], s[y - x + 1]);
    return 0;
}