标签: 动态规划

[HAOI2010]计数 题解

[HAOI2010]计数 题解

题目地址:洛谷:【P2518】[HAOI2010]计数 – 洛谷、BZOJ:Problem 2425. — [HAOI2010]计数

题目描述

你有一组非零数字(不一定唯一),你可以在其中插入任意个0,这样就可以产生无限个数。比如说给定{1,2},那么可以生成数字12,21,102,120,201,210,1002,1020,等等。
现在给定一个数,问在这个数之前有多少个数。(注意这个数不会有前导0).

输入输出格式

输入格式:
只有1行,为1个整数n.

输出格式:
只有整数,表示N之前出现的数的个数。

输入输出样例

输入样例#1:

1020

输出样例#1:

7

说明

n的长度不超过50,答案不超过2^63-1.

题解

参考资料:题解 P2518 【[HAOI2010]计数】 – noob – 洛谷博客
如果我们不删去前导0,其实就是相当于求对给定数的全排列中,比这个数小的排列个数。考虑一个数位DP的思想,如果一个高位上填的数字已经更小了,后面的位置显然是可以瞎邒排的,直接往答案里加一个全排列就好。但是可重集的全排列是有可能爆LL的,这里我们采用一种折中的办法求:如果集合大小为n,每个数码的个数为xi,考虑从n个位置里先取x0个位置放置0,然后从n-x0个位置放置1,,以此类推。也就是说,上面那个可重集的全排列数量是
\prod_{i=0}^9 \mathrm{C}_{n - \sum_{j=0}^{i-1} x_j}^{x_i}
我们枚举若前面的所有数位都与原数相同且现在这一位填某数码的时候计算答案,加进去就可以了。

代码

// Code by KSkun, 2018/5
#include <cstdio>
#include <cctype>
#include <cstring>

typedef long long LL;

const int MAXN = 55;

LL C[MAXN][MAXN];

inline void calc() {
    C[0][0] = 1;
    for(int i = 1; i < MAXN; i++) {
        C[i][0] = 1;
        for(int j = 1; j <= i; j++) {
            C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
        }
    }
}

char num[MAXN];
int n, cnt[10];

inline LL cal(int n, int *cnt) {
    LL res = 1;
    for(int i = 0; i <= 9; i++) {
        res *= C[n][cnt[i]];
        n -= cnt[i];
    }
    return res;
}

int main() {
    calc();
    scanf("%s", num + 1); n = strlen(num + 1);
    for(int i = 1; i <= n; i++) {
        cnt[num[i] - '0']++;
    }
    LL ans = 0;
    for(int i = 1; i <= n; i++) {
        for(int j = 0; j < num[i] - '0'; j++) {
            if(cnt[j]) {
                cnt[j]--;
                ans += cal(n - i, cnt);
                cnt[j]++;
            }
        }
        cnt[num[i] - '0']--;
    }
    printf("%lld", ans);
    return 0;
}
[HAOI2011]problem a 题解

[HAOI2011]problem a 题解

题目地址:洛谷:【P2519】[HAOI2011]problem a – 洛谷、BZOJ:Problem 2298. — [HAOI2011]problem a

题目描述

一次考试共有n个人参加,第i个人说:“有ai个人分数比我高,bi个人分数比我低。”问最少有几个人没有说真话(可能有相同的分数)

输入输出格式

输入格式:
第一行一个整数n,接下来n行每行两个整数,第i+1行的两个整数分别代表ai、bi

输出格式:
一个整数,表示最少有几个人说谎

输入输出样例

输入样例#1:

3
2 0
0 2
2 2

输出样例#1:

1

说明

100%的数据满足: 1≤n≤100000 0≤ai、bi≤n

题解

不好求最少说谎人数,转而求最大没说谎人数。
考虑说谎是怎么说的:

  1. 对于说自己前面有a人,后面有b人且a+b+1>n的人,他肯定在说谎,直接跳过它
  2. 对于所有说自己前面有a人,后面有b人的人,如果人数超过了n-a-b,那么人数-(n-a-b)这些人肯定在说谎

第一个可以在读入的时候顺手处理了,而第二个需要用动态规划。
把每个前面有a人,后面有b人的信息处理成成绩所在的区间为[a+1, n-b]这样的形式,并统计这个区间上实际有多少人,问题就变成了,每个区间有一个权值,选择不重叠的区间让权值最大。这个可以用DP做,令dp[i]表示选择完[1, i]这里面的区间后,权值和最大的值。对于右端点i,我可以不选区间,则从dp[i-1]转移而来;选择区间,则从选择的区间的左边转移而来,也就是说
\displaystyle dp[i] = \max \begin{cases} dp[i-1] & \text{不选择右端点为i的区间} \\ dp[\text{选择区间左端点}-1] + \text{选择区间的权值} & \text{选择右端点为i的区间} \end{cases}
我们用一个vector数组,vec[i]表示右端点为i的区间的左端点集合,这样就可以遍历该vector实现转移了。而权值的统计,实际上是输入数据中每个区间的出现次数,用个map搞一搞。注意如果权值超过区间长度,要跟区间长度取个min。

代码

学习了一波题解 P2519 【[HAOI2011]problem a】 – rushcheyo 的博客 – 洛谷博客的写法,感谢这位同学。

// Code by KSkun, 2018/5
#include <cstdio>
#include <cctype>

#include <algorithm>
#include <vector>
#include <map>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    register char c = fgc();
    while(!isdigit(c)) {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(isdigit(c)) {
        res = (res << 1) + (res << 3) + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 100005;

int n;

struct Node {
    int s, t;
    inline bool operator<(const Node &rhs) const {
        return t == rhs.t ? s < rhs.s : t < rhs.t;
    }
};

std::vector<int> vec[MAXN];
std::map<Node, int> cnt;

int dp[MAXN];

int bef, aft;

int main() {
    n = readint();
    for(int i = 1; i <= n; i++) {
        bef = readint(); aft = readint();
        if(bef + aft + 1 > n) continue;
        if(++cnt[Node {bef + 1, n - aft}] == 1) {
            vec[n - aft].push_back(bef + 1);
        }
    }
    for(int i = 1; i <= n; i++) {
        dp[i] = dp[i - 1];
        for(int j = 0; j < vec[i].size(); j++) {
            dp[i] = std::max(dp[i], 
                dp[vec[i][j] - 1] + std::min(i - vec[i][j] + 1, cnt[Node {vec[i][j], i}]));
        }
    }
    printf("%d", n - dp[n]);
    return 0;
}
[ZJOI2006]物流运输 题解

[ZJOI2006]物流运输 题解

题目地址:洛谷:【P1772】[ZJOI2006]物流运输 – 洛谷、BZOJ:Problem 1003. — [ZJOI2006]物流运输

题目描述

物流公司要把一批货物从码头A运到码头B。由于货物量比较大,需要n天才能运完。货物运输过程中一般要转停好几个码头。物流公司通常会设计一条固定的运输路线,以便对整个运输过程实施严格的管理和跟踪。由于各种因素的存在,有的时候某个码头会无法装卸货物。这时候就必须修改运输路线,让货物能够按时到达目的地。但是修改路线是—件十分麻烦的事情,会带来额外的成本。因此物流公司希望能够订一个n天的运输计划,使得总成本尽可能地小。

输入输出格式

输入格式:
物流公司要把一批货物从码头A运到码头B。由于货物量比较大,需要n天才能运完。货物运输过程中一般要转停好几个码头。物流公司通常会设计一条固定的运输路线,以便对整个运输过程实施严格的管理和跟踪。由于各种因素的存在,有的时候某个码头会无法装卸货物。这时候就必须修改运输路线,让货物能够按时到达目的地。但是修改路线是—件十分麻烦的事情,会带来额外的成本。因此物流公司希望能够订一个n天的运输计划,使得总成本尽可能地小。

输出格式:
包括了一个整数表示最小的总成本。总成本=n天运输路线长度之和+K*改变运输路线的次数。

输入输出样例

输入样例#1:

5 5 10 8
1 2 1
1 3 3
1 4 2
2 3 2
2 4 4
3 4 1
3 5 2
4 5 2
4
2 2 3
3 1 1
3 3 3
4 4 5

输出样例#1:

32

题解

n和m非常小,尤其是m,所以可以乱搞。我们考虑预处理出一个数组dist[i][j]表示时间段[i, j]内不修改路线的最短路,这个预处理可以[eq]O(n^2)[/eq]枚举时间段每次判断该时间段内哪些点始终可用,并且跑一遍SPFA。后面就是DP,每次的决策是在何处改线路,dp[i]表示时间段[1, i]最小开销,枚举一个j<i,表示在j时间处改变路线,转移如下
\displaystyle dp[i] = \min\{dist[1][i], \min\{dp[j]+k+dist[j+1][i] \cdot (i-j)\}\}
答案即为dp[n]。

代码

// Code by KSkun, 2018/5
#include <cstdio>
#include <cctype>
#include <cstring>

#include <vector>
#include <queue>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF 
        : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    register char c = fgc();
    while(!isdigit(c)) {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(isdigit(c)) {
        res = (res << 1) + (res << 3) + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 105;

struct Edge {
    int to, w;
};

std::vector<Edge> gra[MAXN];

int n, m, k, e, d;

int dis[MAXN];
bool del[MAXN][MAXN], ndel[MAXN], inque[MAXN];
std::queue<int> que;

inline int spfa(int s, int t) {
    memset(dis, 0x3f, sizeof(dis));
    memset(ndel, 0, sizeof(ndel));
    for(int i = 1; i <= m; i++) {
        for(int j = s; j <= t; j++) {
            if(del[i][j]) {
                ndel[i] = true; break;
            }
        }
    }
    dis[1] = 0; inque[1] = true; que.push(1);
    while(!que.empty()) {
        int u = que.front(); que.pop(); inque[u] = false;
        for(int i = 0; i < gra[u].size(); i++) {
            int v = gra[u][i].to;
            if(ndel[v]) continue;
            if(dis[v] > dis[u] + gra[u][i].w) {
                dis[v] = dis[u] + gra[u][i].w;
                if(!inque[v]) {
                    inque[v] = true; que.push(v);
                }
            }
        }
    }
    return dis[m];
}

int dist[MAXN][MAXN];
LL dp[MAXN];

int main() {
    n = readint(); m = readint(); k = readint(); e = readint();
    for(int i = 1, u, v, w; i <= e; i++) {
        u = readint(); v = readint(); w = readint();
        gra[u].push_back(Edge {v, w});
        gra[v].push_back(Edge {u, w});
    }
    d = readint();
    for(int i = 1, u, a, b; i <= d; i++) {
        u = readint(); a = readint(); b = readint();
        for(int j = a; j <= b; j++) {
            del[u][j] = true;
        }
    }
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= n; j++) {
            dist[i][j] = spfa(i, j);
        }
    }
    for(int i = 1; i <= n; i++) {
        dp[i] = 1ll * dist[1][i] * i;
        for(int j = 1; j < i; j++) {
            dp[i] = std::min(dp[i], dp[j] + k + 1ll * dist[j + 1][i] * (i - j));
        }
    }
    printf("%lld", dp[n]);
    return 0;
}