好题选讲(4)

[hdu3401]Trade

Description

有 $T$ 天,每天你可以以每股 $ap_i$ 的价格买进至多 $as_i$ 股股票,和以每股 $bp_i$ 的价格卖出至多 $bs_i$ 股股票,任何时刻最多拥有 $maxp$ 股,交易股票后 $w$ 天不能再交易股票,求能赚到的最多的钱。

$T\le2000,bp_i\le ap_i\le 1000,bs_i,as_i,maxp \le2000$

Sol

先推转移方程式,设 $f_{i,j}$ 表示第 $i$ 天拥有 $k$ 股股票所赚的最多的钱,考虑 $4$ 种情况。

- 原来没有股票,要买进:

$$f_{i,j}=-j*ap_i$$

- 不买也不卖

$$f_{i,j}=f_{i-1,j}$$

- 在之前的基础上买股票

$$f_{i,j}=\max\{f_{i-w-1,k} - (j-k)\times as_i\},k\in[1,j)$$

- 在之前的基础上卖股票

$$f_{i,j}=\max\{f_{i-w-1,k}+(k-j)\times bs_i\},k\in[j+1,maxp]$$

观察到题面只说了 $w$ 天之后可以买股票,但具体是哪一天是变化的,所以第一种情况就相当于将之前不买的情况转移到下一天,然后情况 $3,4$ 中只枚举 $f_{i-w-1}$ 就是对的。

直接枚举是 $\mathcal{O}(n^3)$ 的,可以每次使用一个单调队列存储 $f_{i-w-1,k}$ 来将时间复杂度优化到 $\mathcal{O}(n^2)$

Code

#include<bits/stdc++.h>
using namespace std;
int Read() {
    int x = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)) {if(ch == '-')  f = -1; ch = getchar();}
    while(isdigit(ch)) {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();}
    return x * f;
}
int n, mx, w, ap[2005], bp[2005], as[2005], bs[2005];
int f[2005][2005], q[2005], head = 1, tail;
signed main() {
    memset(f, 128, sizeof(f));
    n = Read(), mx = Read(), w = Read();
    for(int i = 1; i <= n; i++)
        ap[i] = Read(), bp[i] = Read(), as[i] = Read(), bs[i] = Read();
    for(int i = 1; i <= n; i++) {
        for(int j = 0; j <= as[i]; j++)  f[i][j] = -j * ap[i];
        for(int j = 0; j <= mx; j++)  f[i][j] = max(f[i][j], f[i - 1][j]);
        if(i <= w)  continue;
        head = 1, tail = 0;
        for(int j = 0; j <= mx; j++) {
            while(head <= tail && q[head] < j - as[i])  ++head;
            while(head <= tail && f[i - w - 1][q[tail]] + q[tail] * ap[i] <= f[i - w - 1][j] + j * ap[i])  --tail;
            q[++tail] = j;
            if(head <= tail)  f[i][j] = max(f[i][j], f[i - w - 1][q[head]] + (q[head] - j) * ap[i]);
        }
        head = 1, tail = 0;
        for(int j = mx; j >= 0; j--) {
            while(head <= tail && q[head] > j + bs[i])  ++head;
            while(head <= tail && f[i - w - 1][q[tail]] + q[tail] * bp[i] <= f[i - w - 1][j] + j * bp[i])  --tail;
            q[++tail] = j;
            if(head <= tail)  f[i][j] = max(f[i][j], f[i - w - 1][q[head]] + (q[head] - j) * bp[i]);
        }
    }
    cout << f[n][0] << endl;
    return 0;
}
View Code

[hdu2191]悼念512汶川大地震遇难同胞——珍惜现在,感恩生活

Description

有 $n$ 元,有 $m$ 种大米,只能整袋购买,价格为 $c_i$,重量为 $w_i$,问最多可以买多少重量。

$n,m\le100$

Sol

多重背包裸题。

Code

#include<bits/stdc++.h>
using namespace std;
int Read() {
    int x = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)) {if(ch == '-')  f = -1; ch = getchar();}
    while(isdigit(ch)) {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();}
    return x * f;
}
int n, m, f[105];
signed main() {
    int T = Read();
    while(T--) {
        n = Read(), m = Read();
        memset(f, 0, sizeof(f));
        for(int i = 1; i <= m; i++) {
            int p = Read(), h = Read(), c = Read();
            for(int j = 1; j <= c; j++)
                for(int k = n; k >= p; k--)
                    f[k] = max(f[k], f[k - p] + h);
        }
        int maxn = 0;
        for(int i = 1; i <= n; i++)  maxn = max(maxn, f[i]);
        cout << maxn << endl;
    }
    return 0;
}
View Code

[bzoj3688]折线统计

Description

给你 $n$ 个点,定义一个点集的权值 $f(S)$ 为其中连续上升与连续下降的折线段数,求 $f(S)=k$ 的点集 $S$ 的个数。

$n\le 5\times10^4,k\le10$

Sol

先推 DP 方程式,设 $f_{i,j,0/1}$ 表示当前点集为 $i$ ,最后一个点为 $j$ ,最后一段为上升或下降的方案数。

那么有 $$f_{i,j,0}=f_{i-1,k,1}+f_{i,k,0}$$

$$f_{i,j,1}=f_{i-1,k,0}+f_{i,k,1}$$

其中 $k$ 为与 $j$ 的高度关系能够满足上升/下降的点。

所以我们用一个权值树状数组记录纵坐标小于等于某个值的点的总数,处理询问时直接根据 $k,j$ 的高度关系在树状数组利查询即可。

时间复杂度 $\mathcal{O}(n\log n)$

Code

#include<bits/stdc++.h>
#define Mod 100007
using namespace std;
int Read() {
    int x = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)) {if(ch == '-')  f = -1; ch = getchar();}
    while(isdigit(ch)) {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();}
    return x * f;
}a
int n, k, dp[500005][12][2], c[500005][12][2];
struct node {
    int x, y;
    bool operator < (node A) const {
        return x < A.x;
    }
}p[500005];
int lowbit(int x) {return x & (-x);}
void Add(int x, int a, int b, int v) {
    for(int i = x; i <= 100000; i += lowbit(i))  c[i][a][b] = (c[i][a][b] + v) % Mod;
}
int query(int x, int a, int b) {
    int ans = 0;
    for(int i = x; i; i -= lowbit(i))  ans = (ans + c[i][a][b]) % Mod;
    return ans;
}
signed main() {
    n = Read(), k = Read(); int ans = 0;
    for(int i = 1; i <= n; i++)  p[i].x = Read(), p[i].y = Read();
    sort(p + 1, p + n + 1);
    for(int i = 1; i <= n; i++) {
        dp[i][0][0] = dp[i][0][1] = 1;
        Add(p[i].y, 0, 0, 1); Add(p[i].y, 0, 1, 1);
        for(int j = 1; j <= k; j++) {
            dp[i][j][0] = (dp[i][j][0] + query(p[i].y - 1, j, 0) + query(p[i].y - 1, j - 1, 1)) % Mod;
            dp[i][j][1] = (dp[i][j][1] + query(100000, j, 1) - query(p[i].y, j, 1) + query(100000, j - 1, 0) - query(p[i].y, j - 1, 0)) % Mod;
            if(dp[i][j][1] < 0)  dp[i][j][1] += Mod;
            Add(p[i].y, j, 0, dp[i][j][0]); Add(p[i].y, j, 1, dp[i][j][1]);
        }
    }
    for(int i = 1; i <= n; i++)  ans = (ans + dp[i][k][0] + dp[i][k][1]) % Mod;
    cout << ans << endl;
    return 0;
}
View Code

[hdu5435]A serious math problem

Description

给定 $a,b$,设 $f(A)$ 表示 $A$ 的所有数位的异或和,求 $f(a)+f(a+1)+...+f(b)$ 的值。

$a,b\le10^{100000}$

Sol

设 $f_{i,j}$ 表示一共枚举了前 $i$ 位,这 $i$ 位的异或和为 $j$,那么我们枚举下一位 $k$ 来转移,转移方程式为 $f_{i+1,j}=\sum f_{i,j^k}$,用 dfs 记搜一下就好了。

注意 dfs 枚举当前长度需要从 $len$ 到 $1$ 记录而不是相反顺序,这样就可以不用清空 dp 数组,使得复杂度与数据组数无关。

Code

#include<bits/stdc++.h>
#define re register
#define Mod 1000000007
using namespace std;
int Read() {
    int x = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)) {if(ch == '-')  f = -1; ch = getchar();}
    while(isdigit(ch)) {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();}
    return x * f;
}
int Add(int a, int b) {return (a + b > Mod) ? a + b - Mod : a + b;}
int Dec(int a, int b) {return (a - b < 0) ? a - b + Mod : a - b;}
int mul(int a, int b) {return 1ll * a * b % Mod;}
int len, f[100005][16], dp[100005][16];
char a[100005], b[100005];
inline int dfs(char *s, int pos, int sum, int qd0, int lim) {
    if(pos == 0)  return sum;
    if(~dp[pos][sum] && !qd0 && !lim)  return dp[pos][sum];
    int res = 0, End = lim ? s[len - pos + 1] - '0' : 9;
    for(re int i = 0; i <= End; i++)
       res = Add(res, dfs(s, pos - 1, sum ^ i, (qd0 & (i == 0)), (lim & (i == End))));
    if(!qd0 && !lim)  return dp[pos][sum] = res;
    else  return res;
}
inline int Calc(char *s) {
    len = strlen(s + 1);
    return dfs(s, len, 0, 1, 1);
}
signed main() {
    memset(dp, -1, sizeof(dp));
    int T = Read();
    for(re int k = 1; k <= T; k++) {
        scanf("%s%s", a + 1, b + 1);
        len = strlen(a + 1); int res = 0;
        for(re int i = 1; i <= len; i++)  res = res ^ (a[i] - 48);
        printf("Case #%d: %d\n", k, Dec(Add(Calc(b), res), Calc(a)));
    }
    return 0;
}
View Code

[hdu5434]Peace small elephant

Description

给你一个 $n\times m$ 的棋盘,定义小象的攻击范围为它的斜对角的四格,有公共边的小象可以合体,攻击范围为合体后的图形的斜对角,问有多少种摆放方式可以使小象互不攻击。

$n\le 10^9,m\le7$

Sol

观察到 $m$ 很小,可以状压,我们暴力枚举上下两行的状态,可以发现如果两行能够匹配的条件是如果点 $(i,j)$ 与点 $(i+1,j+1)$ 均为小象,那么点 $(i,j+1)$ 与点 $(i+1,j)$ 必须要有至少一个点是小象,点$(i+1,j-1)$ 同理,预处理出所有可以匹配的方式后用矩阵乘法优化即可。

Code

#include<bits/stdc++.h>
#define int long long
#define Mod 1000000007
using namespace std;
int Read() {
    int x = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)) {if(ch == '-')  f = -1; ch = getchar();}
    while(isdigit(ch)) {x = (x << 3) + (x << 1) + ch - '0'; ch = getchar();}
    return x * f;
}
int n, m, lim, cn[155][155];
bool chk(int x, int y) {
    for(int i = 0; i < m; i++) {
        if(!(y & (1 << i)))  continue;
        if(i && x & (1 << (i - 1)) && (!(x & (1 << i)) && !(y & (1 << (i - 1)))))  return 0;
        if(i != m && x & (1 << (i + 1)) && (!(x & (1 << i)) && !(y & (1 << (i + 1)))))  return 0;
    }
    return 1;
}
struct martix {
    int a[155][155];
    void init(int x) {
        memset(a, 0, sizeof(a));
        for(int i = 0; i < lim; i++)  a[i][i] = x;
    }
    friend martix operator * (martix A, martix B) {
        martix C; C.init(0);
        for(int i = 0; i < lim; i++)
            for(int j = 0; j < lim; j++)
                for(int k = 0; k < lim; k++)
                    (C.a[i][j] += A.a[i][k] * B.a[k][j]) %= Mod;
        return C;
    }
    friend martix operator ^ (martix A, int B) {
        martix C; C.init(1);
        while(B) {
            if(B & 1)  C = C * A;
            A = A * A;
            B >>= 1;
        }
        return C;
    }
};
signed main() {
    while(~scanf("%lld%lld", &n, &m)) {
        lim = (1 << m);
        martix A, B; A.init(0); B.init(0);
        for(int i = 0; i < (1 << m); i++) {
            for(int j = 0; j < (1 << m); j++)
                if(chk(i, j))  A.a[i][j] = 1;
        }
        B.a[0][0] = 1;
        B = B * (A ^ n);
        int ans = 0;
        for(int i = 0; i < lim; i++)
            for(int j = 0; j < lim; j++)
                (ans += B.a[i][j]) %= Mod;
        cout << ans << endl;
    }
    return 0;
}
View Code
posted @ 2020-09-25 16:44  verjun  阅读(127)  评论(0编辑  收藏  举报