SMU 2023 Spring 题单 第一周 DP

Cow Bowling

简单 dp,f[i][j]到达第i行第j列的最大值

#include<iostream>
#include <vector>
#include <algorithm>

using namespace std;

#define int long long

int read() {
    int x = 0, f = 1, ch = getchar();
    while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
    if (ch == '-') f = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}

int32_t main() {
    int n = read();
    vector<vector<int> > f(n + 1, vector<int>(n + 1, 0));
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= i; j++) f[i][j] = read() + max(f[i - 1][j], f[i - 1][j - 1]);
    cout << *max_element(f[n].begin(), f[n].end());
    return 0;
}

Sumsets

完全背包

复杂度\(O(N\log N)\)

#include<iostream>
#include <vector>
#include <algorithm>

using namespace std;

const int mod = 1e9, N = 1e6+5;
int n , f[N];

int32_t main() {
    cin >> n;
    f[0] = 1;
    for (int i = 1 ; i <= n ; i <<= 1 )
        for (int j = i; j <= n; j++)
            f[j] = (f[j] + f[j-i]) % mod;
    cout << f[n];
    return 0;
}

线性递推

f[i]表示构成i的方案数

如果i是奇数,则一定包含1,因为 2 的整次幂中只有 1 是奇数

如果i是偶数则分类讨论,看是否包含 1,如果包含 1 则一定是从f[i-1]转移过来

#include<iostream>
#include <vector>
#include <algorithm>

using namespace std;

#define int long long

const int mod = 1e9;

int32_t main() {
    int n;
    cin >> n;
    vector<int> f(n+1 , 0 );
    f[0] = 1;
    for (int i = 1; i <= n; i++){
        if( i & 1 ) f[i] = f[i-1];
        else f[i] = (f[i-1] + f[i/2]) % mod;
    }

    cout << f[n] % mod;
    return 0;
}

Apple Catching

f[i][j][0/1]表示第i秒交换j次在0/1树下的最优解

注意状态f[1][0][1],f[1][1][0]是非法状态

#include <iostream>

using namespace std;

int read() {
    int x = 0, f = 1, ch = getchar();
    while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
    if (ch == '-') f = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}

const int N = 1005, M = 35;
int a[N][2], f[N][M][2];

int32_t main() {
    int n = read(), m = read();
    for (int i = 1, x; i <= n; i++)
        x = read() - 1, a[i][x] = 1;

    f[1][0][0] = a[1][0], f[1][1][1] = a[1][1], f[1][0][1] = f[1][1][0] = -0x7f7f7f7f;
    for (int i = 2; i <= n; i++) {
        f[i][0][0] = f[i - 1][0][0] + a[i][0];
        f[i][0][1] = f[i - 1][0][1] + a[i][1];
        for (int j = 1; j <= min(i, m); j++) {
            f[i][j][1] = max(f[i - 1][j][1], f[i - 1][j - 1][0]) + a[i][1];
            f[i][j][0] = max(f[i - 1][j][0], f[i - 1][j - 1][1]) + a[i][0];
        }
    }
    int res = 0;
    for (int i = 0; i <= m; i++)
        res = max(res, max(f[n][i][0], f[n][i][1]));
    cout << res;
    return 0;
}

Milking Time

其实这里给的时间范围是一个无用的变量\(f[i]\)表示以第\(i\)个区间结尾的最大值,则\(f[i]=\max(f[j]+efficiency_i),j\in(starting_i\ge ending_j)\)

为了方便枚举,把区间按照开始时间排序即可

注意答案是\(\max(f[i])\)

#include <iostream>
#include <vector>
#include <cstdio>
#include <climits>
#include <algorithm>
using namespace std;

#define int long long

int read() {
    int x = 0, f = 1, ch = getchar();
    while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
    if (ch == '-') f = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}

struct T {
    int l, r, w;

    T() {};

    T(int l, int r, int w) : l(l), r(r), w(w) {};

    bool operator<(T b) const {
        if (l != b.l) return l < b.l;
        return r < b.r;
    }
};

int32_t main() {
    int n = read(), m = read(), t = read();
    vector<int> f(m, INT_MIN);
    vector<T> v(m);
    for (int i = 0; i < m; i++)
        v[i].l = read(), v[i].r = read() + t, v[i].w = read();
    sort(v.begin(), v.end());
    f[0] = 0;
    for (int i = 0; i < m; i++) {
        f[i] = v[i].w;
        for (int j = 0; j < i; j++) {
            if (v[j].r > v[i].l) continue;
            f[i] = max(f[i], f[j] + v[i].w);
        }
    }
    int res = INT_MIN;
    for (int i = 0; i < m; i++) res = max(res, f[i]);
    printf("%lld\n", res);

}

Cheapest Palindrome

区间 dp 的典题

首先增加字符和减少字符可以看做等价条件,所以在选择操作是,我们选择便宜的就好,对于字母\(i\)的操作代价记作v[i]

f[l][r]为把区间[l,r]变成回文串的最小操作次数。

如果s[l]==s[r]则不需要操作,直接f[l][r]=f[l+1][r-1]

反之有两种操作方法,一种是操作l花费是f[l+1][r]+v[l],另一种是操作r花费是f[l][r-1]+v[r]两个取较小值就好。

#include <iostream>
#include <vector>
#include <cstdio>
#include <climits>
#include <algorithm>

using namespace std;

#define int long long

int read() {
    int x = 0, f = 1, ch = getchar();
    while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
    if (ch == '-') f = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}

const int N = 2005;
int n, m, v[N], f[N][N];
string s;

int32_t main() {
    cin >> n >> m >> s;
    for (int i = 1, x, y; i <= n; i++) {
        char c;
        cin >> c >> x >> y;
        v[c] = min(x, y);
    }
    for( int r = 1 ; r < m ; r ++ )
        for( int l = r-1 ; l >= 0 ; l -- ){
            if( s[l] == s[r] ) f[l][r] = f[l+1][r-1];
            else f[l][r] = min( f[l+1][r] + v[s[l]] , f[l][r-1] + v[s[r]] );
        }
    cout << f[0][m-1];
    return 0;
}

Coins

多重背包、完全背包

显然是一个多重背包的题目,用二进制拆分做一下就好了。但是 poj 卡常数,要把\(A_i\times C_i > m\)的情况当成完全背包做才能过。

#include <iostream>
#include <vector>
#include <cstdio>
#include <climits>
#include <algorithm>

using namespace std;

int read() {
    int x = 0, f = 1, ch = getchar();
    while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
    if (ch == '-') f = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}

const int N = 1e5 + 5;
int a[N], f[N], v[N];


int32_t main() {
    int n, m, tot, res;
    while (true) {
        n = read(), m = read();
        if (n + m == 0) return 0;

        for (int i = 1; i <= m; i++) f[i] = 0;
        f[0] = 1, res = 0;

        for (int i = 1; i <= n; i++) a[i] = read();
        for (int i = 1, x, t; i <= n; i++) {
            x = read();
            if (x * a[i] >= m) {
                for( int j = a[i] ; j <= m ; j ++ )
                    f[j] |= f[j-a[i]];
            } else {
                t = 1, tot = 0;
                while (x > t) v[++tot] = a[i] * t, x -= t, t <<= 1;
                if (x > 0) v[++tot] = x * a[i];
                for (int k = 1; k <= tot; k++)
                    for (int j = m; j >= v[k]; j--)
                        f[j] |= f[j - v[k]];
            }
        }
        for (int i = 1; i <= m; i++) res += f[i];
        printf("%d\n", res);
    }
    return 0;
}

多重背包、可行性分析

这里的做法是首先贪心的使用i之前的硬币来组成j,其次是在used限制的情况下把多重背包转换成完全背包,一次来大幅度提升效率

#include <iostream>
#include <vector>
#include <cstdio>
#include <climits>
#include <algorithm>
#include <cstring>

using namespace std;

int read() {
    int x = 0, f = 1, ch = getchar();
    while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
    if (ch == '-') f = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}

const int N = 1e5 + 5, T = 1005;
int a[N], f[N], used[N];


int32_t main() {
    int n, m, res;
    while (true) {
        n = read(), m = read();
        if (n + m == 0) return 0;

        memset(f, 0, sizeof f);
        f[0] = 1 , res = 0;
        for (int i = 1; i <= n; i++) a[i] = read();
        for (int i = 1, x; i <= n; i++) {
            memset(used, 0, sizeof used);
            x = read();
            for (int j = a[i]; j <= m; j++)
                if (!f[j] && f[j - a[i]] && used[j - a[i]] < x)
                    f[j] = 1, used[j] = used[j - a[i]] + 1;
        }
        for (int i = 1; i <= m; i++) res += f[i];
        printf("%d\n", res);
    }
    return 0;
}

Ant Counting

\(f[i][j]\)表示前\(i\)中选\(k\)个的方案数,容易可以得到\(f[i][j]=\sum_{k=0}^{\min(j,a[i])} f[i-1][j-k]\)这样的递推式,但是这个递推的求解是\(O(N^3)\)

\[f[i][j] = f[i-1][j] + f[i-1][j-1] + \cdots+ f[i-1][j-a[i]] \\+ (f[i-1][j-a[i]-1] - f[i-1][j-a[i]-1])\\ = f[i-1][j] + f[i][j-1] - f[i-1][j-a[i] -1] \]

这样可以把\(dp\)的过程转化成\(O(N^2)\)

#include <iostream>
#include <vector>
#include <cstdio>
#include <climits>
#include <algorithm>
#include <cstring>

using namespace std;

int read() {
    int x = 0, f = 1, ch = getchar();
    while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
    if (ch == '-') f = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}

const int T = 1005, N = 100005 ,mod = 1e6;
int cnt[T], f[T][N];

int32_t main() {
    int t = read(), n = read(), a = read(), b = read();
    for (int x, i = 1; i <= n; i++) x = read(), cnt[x]++;
    f[0][0] = 1;
    for (int i = 1; i <= t; i++) {
        f[i][0] = 1;
        for (int j = 1; j <= n; j++) {
            if( j - 1 >= cnt[i] ) f[i][j] = (f[i][j-1] + f[i-1][j] - f[i-1][j-1-cnt[i]] + mod ) % mod;
            else f[i][j] = ( f[i][j-1] + f[i-1][j] ) % mod;
        }
    }
    int res = 0;
    for (int i = a; i <= b; i++)
        res += f[t][i];
    cout << res % mod;
    return 0;
}

Dollar Dayz

完全背包,会爆long long

两种做法 __int128

#include <iostream>
#include <vector>
#include <cstdio>
#include <climits>
#include <algorithm>
#include <cstring>

using namespace std;

#define int long long

const int N = 1005;

int read() {
    int x = 0, f = 1, ch = getchar();
    while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
    if (ch == '-') f = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}

void write( __int128 x ){
    string s = "";
    while( x > 0 )
        s += char((x%10)+'0') , x /= 10;
    reverse(s.begin(), s.end());
    cout << s;
}

int n , m ;

__int128 f[N];

int32_t main() {
    n = read() , m = read();
    f[0] = 1;
    for( int i = 1 ; i <= m ; i ++ )
        for( int j = i ; j <= n ; j ++ )
            f[j] += f[j-i];
    write(f[n]);
    return 0;
}

两个long long

#include <iostream>
#include <vector>
#include <cstdio>
#include <climits>
#include <algorithm>
#include <cstring>

using namespace std;

#define int long long

const int N = 1105, mod = 1e18;
int n, m, f[N], g[N];

int32_t main() {
    while (scanf("%lld%lld", &n, &m) != EOF) {
        memset(f, 0, sizeof f);
        memset(g, 0, sizeof g);
        f[0] = 1;
        for (int i = 1; i <= m; i++)
            for (int j = i; j <= n; j++)
                g[j] = g[j] + g[j - i] + (f[j] + f[j - i]) / mod, f[j] = (f[j] + f[j - i]) % mod;
        if (g[n] == 0) printf("%I64d\n", f[n]);
        else printf("%I64d%018I64d\n", g[n], f[n]);
        
    }
    return 0;
}

Wooden Sticks

首先把木棍按照长度从小到大排序,然后按照宽度划分出\(x\)个不下降子序列,求\(x\)的最小值。而\(x\)的最小值就是最长下降子序列的长度。证明参考狄尔沃斯定理

#include <cstdio>
#include <utility>
#include <vector>
#include <algorithm>

using namespace std;

int read() {
    int x = 0, f = 1, ch = getchar();
    while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
    if (ch == '-') f = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}

void solve() {
    int n = read();
    vector<pair<int, int> > p(n);
    for( int i = 0 ; i < n ; i ++ ) p[i].first = read() , p[i].second = read();
    sort(p.begin(), p.end());
    vector<int> f(n , 1 );
    for( int i = 1 ; i < n ; i ++ ){
        for( int j = 0 ; j < i ; j ++ )
            if( p[j].second > p[i].second )
                f[i] = max( f[i] , f[j]+1 );
    }
    printf("%d\n" , *max_element(f.begin(), f.end()) );
    return;
}

int32_t main() {
    for (int t = read(); t; t--)
        solve();
    return 0;
}

Bridging signals

写一个最长不上升子序列,然后 TLE 了

#include <cstdio>
#include <utility>
#include <vector>
#include <algorithm>

using namespace std;

int read() {
    int x = 0, f = 1, ch = getchar();
    while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
    if (ch == '-') f = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}

void solve() {
    int n = read();
    vector<int> a(n), f(n, 1);
    for (int i = 0; i < n; i++) a[i] = read();
    for (int i = 1; i < n; i++) {
        for (int j = 0; j < i; j++)
            if (a[j] <= a[i]) f[i] = max(f[i], f[j] + 1);
    }
    printf("%d\n", *max_element(f.begin(), f.end()));
    return;
}

int32_t main() {
    for (int t = read(); t; t--)
        solve();
    return 0;
}

正解就是二分优化一下,首先维护已知的最长不下降子序列a,为了使后面的数字尽可能的放进去就要让当前的序列尽可能的小,所以在放的过程中,如果可以放到尾部就直接放,后缀去里面二分查找大于等于当前数的最小值替换即可

#include <cstdio>
#include <utility>
#include <vector>
#include <algorithm>

using namespace std;

int read() {
    int x = 0, f = 1, ch = getchar();
    while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
    if (ch == '-') f = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}

void solve() {
    int n = read();
    vector<int> a;
    for (int x, t; n; n--) {
        x = read();
        if (a.empty() || a.back() < x) a.push_back(x);
        else {
            t = lower_bound(a.begin(), a.end(), x) - a.begin();
            a[t] = x;
        }
    }
    printf("%d\n", (int) a.size());
    return;
}

int32_t main() {
    for (int t = read(); t; t--)
        solve();
    return 0;
}

Making the Grade

\(f[i][j]\)表示把前\(i\)个转换成不上升的且最后一位是\(j\)的最小花费,则\(f[i][j]=\min(f[i-1][1\cdots j]) + abs( a_i-j)\)

对于求\(\min\)的部分维护一个前缀最小值即可。然后至于不下降,我的做法是吧原数组倒过来再做一遍不上升就好了

注意这题需要对高度进行离散化。并且可以推出结论,最后的数组中的数的取值一定在最开始的数组的数值集合中

#include <cstdio>
#include <vector>
#include <algorithm>

using namespace std;

int read() {
    int x = 0, f = 1, ch = getchar();
    while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
    if (ch == '-') f = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}

const int N = 2005;
int n, m;
int a[N], b[N], c[N];
int f[N][N], minf[N][N];
int g[N][N], ming[N][N];


int32_t main() {
    n = read();
    for (int i = 1; i <= n; i++) a[i] = b[i] = read();
    sort(a + 1, a + 1 + n), m = unique(a + 1, a + 1 + n) - a - 1;
    for (int i = 1, j = n; i <= n; i++, j--)
        b[i] = c[j] = lower_bound(a + 1, a + 1 + m, b[i]) - a;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) {
            f[i][j] = minf[i - 1][j] + abs(a[j] - a[b[i]]);
            g[i][j] = minf[i - 1][j] + abs(a[j] - a[c[i]]);
            if (j == 1) minf[i][j] = f[i][j], ming[i][j] = g[i][j];
            else minf[i][j] = min(minf[i][j - 1], f[i][j]), ming[i][j] = min(ming[i][j - 1], g[i][j]);
        }
    printf("%d\n", min(minf[n][m], ming[n][m]));
    return 0;
}

Space Elevator

首先根据限制高度排序,然后开始做转化成多重背包,这里因为数据范围很小可以直接做。

并且这里的状态设计呀与普通的背包不同,\(f[j]\)表示在\(i\)中积木下到达高度\(j\)能够剩下最多的积木个数,如果值是\(-1\)则代表无法到达。

#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

struct node{
	int h , c , limt;
	bool operator < ( const node & b ) const {
		return limt < b.limt;
	}
};

int read(){
	int ch = getchar() , x = 0;
	while( ch < '0' || ch > '9' ) ch = getchar();
	while( ch >= '0' && ch <= '9' ) x = (x<<3)+(x<<1)+ch-'0' , ch = getchar();
	return x;
}


const int N = 405 , M = 4e4+5;
int f[M], n , m;
node a[N];

int main(){
	n = read();
	for( int i = 1 ; i <= n ; i ++ )
		a[i].h = read() , a[i].limt = read(),a[i].c = read();
	sort( a+1 , a+1+n ) , m = a[n].limt; 
	for( int i = 1 ; i <= m ; i ++ ) f[i] = -1;
	for( int i = 1 ; i <= n ; i ++ ){
		for( int j = 0 ; j <= a[i].limt ; j ++ ){
			if( f[j] >= 0 ) f[j] = a[i].c;
			else if ( j >= a[i].h && f[j-a[i].h] >= 1 ) f[j] = f[j-a[i].h] - 1;
		}
	}
	for( int i = m ; i >= 0 ; i -- )
		if( f[i] >= 0 ) return cout << i , 0;
	return 0;
}
posted @ 2023-03-27 21:08  PHarr  阅读(10)  评论(0编辑  收藏  举报