最新文章

[NOI2015]荷马史诗 题解

[NOI2015]荷马史诗 题解

题目地址:洛谷:【P2168】[NOI2015]荷马史诗 – 洛谷、BZOJ:Problem 4198. — [Noi2015]荷马史诗

题目描述

追逐影子的人,自己就是影子 ——荷马
Allison 最近迷上了文学。她喜欢在一个慵懒的午后,细细地品上一杯卡布奇诺,静静地阅读她爱不释手的《荷马史诗》。但是由《奥德赛》和《伊利亚特》 组成的鸿篇巨制《荷马史诗》实在是太长了,Allison 想通过一种编码方式使得它变得短一些。
一部《荷马史诗》中有n种不同的单词,从1到n进行编号。其中第i种单 词出现的总次数为wi。Allison 想要用k进制串si来替换第i种单词,使得其满足如下要求:
对于任意的 1 ≤ i, j ≤ n , i ≠ j ,都有:si不是sj的前缀。
现在 Allison 想要知道,如何选择si,才能使替换以后得到的新的《荷马史诗》长度最小。在确保总长度最小的情况下,Allison 还想知道最长的si的最短长度是多少?
一个字符串被称为k进制字符串,当且仅当它的每个字符是 0 到 k − 1 之间(包括 0 和 k − 1 )的整数。
字符串 str1 被称为字符串 str2 的前缀,当且仅当:存在 1 ≤ t ≤ m ,使得str1 = str2[1..t]。其中,m是字符串str2的长度,str2[1..t] 表示str2的前t个字符组成的字符串。

题意简述

一篇文章有$n$个单词,你想使用一种压缩算法压缩这篇文章,具体而言,用一个$k$进制数来代替一个单词,其中,任何两个单词的代表数不能一个是另一个前缀。

输入输出格式

输入格式:
输入的第 1 行包含 2 个正整数 n, k ,中间用单个空格隔开,表示共有 n种单词,需要使用k进制字符串进行替换。
接下来n行,第 i + 1 行包含 1 个非负整数wi ,表示第 i 种单词的出现次数。

输出格式:
输出包括 2 行。
第 1 行输出 1 个整数,为《荷马史诗》经过重新编码以后的最短长度。
第 2 行输出 1 个整数,为保证最短总长度的情况下,最长字符串 si 的最短长度。

输入输出样例

输入样例#1:

4 2
1
1
2
2

输出样例#1:

12
2

输入样例#2:

6 3
1
1
3
3
9
9

输出样例#2:

36
3

说明

【样例说明 1】
用 X(k) 表示 X 是以 k 进制表示的字符串。
一种最优方案:令 00(2) 替换第 1 种单词, 01(2) 替换第 2 种单词, 10(2) 替换第 3 种单词,11(2) 替换第 4 种单词。在这种方案下,编码以后的最短长度为:
1 × 2 + 1 × 2 + 2 × 2 + 2 × 2 = 12
最长字符串si的长度为 2 。
一种非最优方案:令 000(2) 替换第 1 种单词,001(2) 替换第 2 种单词,01(2)替换第 3 种单词,1(2) 替换第 4 种单词。在这种方案下,编码以后的最短长度为:
1 × 3 + 1 × 3 + 2 × 2 + 2 × 1 = 12
最长字符串 si 的长度为 3 。与最优方案相比,文章的长度相同,但是最长字符串的长度更长一些。
【样例说明 2】
一种最优方案:令 000(3) 替换第 1 种单词,001(3) 替换第 2 种单词,01(3) 替换第 3 种单词, 02(3) 替换第 4 种单词, 1(3) 替换第 5 种单词, 2(3) 替换第 6 种单词。
对于所有数据,保证 2≤n≤100000,2≤k≤9。
【提示】
选手请注意使用 64 位整数进行输入输出、存储和计算。
【时限1s,内存512M】

题解

$k=2$的时候,就是哈夫曼树的裸题了。现在我们重新考虑哈夫曼树的构建,是从单词中选出两个合并成一个点,作为一个新的点放进原集合中,直至合并为只有1个点,合并的过程其实就是把这些点都接到一个根上,从而形成一棵有根哈夫曼树。本题中,我们可以选$k$个点合并,每个点记录下出现次数及当前的替换字符串长,合并的时候新点的出现次数是原来$k$个点的出现次数之和,串长是原来$k$个点串长最大值+1。如此合并至只剩1个点即可,在合并的过程中维护答案。
但如果单词数量无法刚好合并成一棵满哈夫曼树怎么办?我们考虑用空点补全,这样就不需要特殊处理树不满的情况了。补到$n \bmod (k-1) = 1$即可,因为每次从集合中删$k-1$个点。
复杂度$O(n \log n)$。

代码

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

#include <algorithm>
#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();
    for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
    for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
    return res * neg;
}

const int MAXN = 100005;

int n, k;

typedef std::pair<LL, LL> PII64;
std::priority_queue<PII64, std::vector<PII64>, std::greater<PII64> > pq;

int main() {
    n = readint(); k = readint();
    for(int i = 1; i <= n; i++) {
        LL w = readint();
        pq.push(PII64(w, 0));
    }
    while(k > 2 && n % (k - 1) != 1) {
        pq.push(PII64(0, 0)); n++;
    }
    LL ans1 = 0, ans2 = 0;
    while(pq.size() > 1) {
        PII64 res(0, 0);
        for(int i = 1; i <= k; i++) {
            res.first += pq.top().first;
            res.second = std::max(res.second, pq.top().second + 1);
            pq.pop();
        }
        ans1 += res.first;
        ans2 = std::max(ans2, res.second);
        pq.push(res);
    }
    printf("%lld\n%lld", ans1, ans2);
    return 0;
}
[洛谷4643]【模板】动态dp 题解

[洛谷4643]【模板】动态dp 题解

题目地址:洛谷:【P4643】【模板】动态dp – 洛谷

题目描述

给定一棵 n 个点的树,点带点权。
有 m 次操作,每次操作给定 x, y ,表示修改点 x 的权值为 y 。
你需要在每次操作之后求出这棵树的最大权独立集的权值大小。

输入输出格式

输入格式:
第一行, n, m ,分别代表点数和操作数。
第二行, V1, V2, …, Vn ,代表 n 个点的权值。
接下来 n-1 行, x, y ,描述这棵树的 n-1 条边。
接下来 m 行, x, y ,修改点 x 的权值为 y 。

输出格式:
对于每个操作输出一行一个整数,代表这次操作后的树上最大权独立集。
保证答案在 int 范围内

输入输出样例

输入样例#1:

10 10
-11 80 -99 -76 56 38 92 -51 -34 47 
2 1
3 1
4 3
5 2
6 2
7 1
8 2
9 4
10 7
9 -44
2 -17
2 98
7 -58
8 48
3 99
8 -61
9 76
9 14
10 93

输出样例#1:

186
186
190
145
189
288
244
320
258
304

说明

对于30%的数据, 1≤n,m≤10
对于60%的数据, 1≤n,m≤1000
对于100%的数据, 1≤n,m≤10^5

题解

参考资料:Luogu P4643 【模板】动态dp – 胡小兔 – 博客园
首先这个题不带修改就是一个“没有上司的舞会”模型,用$dp[u][0/1]$代表$u$这个点选或不选其子树中的最大权独立集的权值和,DFS向上转移即可。
现在我们需要动态地维护树上的DP值,我们需要一种方法来使权值能快速更新。在这道题里,我们的转移方程是
$$ \begin{aligned}
dp[u][0] &= \sum_{v \text{ is a son of } u} \max \{ dp[v][0], dp[v][1] \} \\
dp[u][1] &= \sum_{v \text{ is a son of } u} dp[v][0]
\end{aligned} $$
首先不好搞的是儿子的数量比较多求和不好做,我们先考虑把树给剖掉,使用$g[u][0/1]$记录$u$这个点选或不选其自身和所有轻儿子的答案的和,而$f[u][0/1]$则记录总的答案,即合并轻重儿子考虑。现在,方程变成了这样
$$ \begin{aligned}
f[u][0] &= g[u][0] + \max \{ f[v][0], f[v][1] \} \\
f[u][1] &= g[u][1] + f[v][0]
\end{aligned} $$
其中的$v$是$u$的重儿子。到现在为止,似乎也没有把转移的形式变得可以树上操作,而且怎么求$g[u][0/1]$的值也不知道。这时,我们想到了矩阵。
矩阵的乘法可以理解为向量运算,即$\mathbf{A} \times \mathbf{B}$可以理解为$\mathbf{B}$中的每一个行向量$\mathbf{b_1}$乘上$\mathbf{A}$中每一行对应的系数$\{ a_{1, 1}, a_{1, 2}, \dots, a_{1, n} \}$再加起来得到的行向量组(或者也可以用列向量的形式理解)。
在这里,我们需要重新定义向量加法和标量乘法。
$$ \begin{aligned}
\mathbf{a} + \mathbf{b} = \mathbf{c} &\Rightarrow c_i = \max \{ a_i, b_i \} \\
\mathbf{a} \times b = \mathbf{c} &\Rightarrow c_i = a_i + b
\end{aligned} $$
我们发现上面的重新定义的运算具有很好的性质,可以按照这样的定义构造向量空间,来定义新的矩阵乘法。新的矩阵乘法如下
$$ \mathbf{A} \times \mathbf{B} = \mathbf{C} \Rightarrow C_{i, j} = \max_k \{ A_{i, k} + B_{k, j} \} $$
这样,我们就可以构造一个矩阵来进行DP的转移了
$$ \begin{bmatrix} f[u][0] \\ f[u][1] \end{bmatrix} = \begin{bmatrix} f[v][0] \\ f[v][1] \end{bmatrix} \times \begin{bmatrix} g[u][0] & g[u][0] \\ g[u][1] & 0 \end{bmatrix} $$
那么,只要我们知道了整棵树上的转移矩阵(即右侧的那个$2 \times 2$矩阵),就可以通过线段树维护重链矩阵乘法,从而快速求某条重链顶端的答案了。我们无需设置一个列向量来乘上这个转移矩阵,这是因为,叶子节点的$f[u][0]=g[u][0]=0, f[u][1]=g[u][1]=w_u$,看上去就跟列向量一样了。
接下来考虑修改问题,我们考虑更新这个结点的矩阵后,沿重链上跳,每个重点的顶的父亲都会受到影响,此时只需要把影响大力算出来改一下就好。
我的初始化是逐个点改权值,复杂度是$O(n \log^2 n)$的,实际上可以先$O(n)$地做一遍DP,然后用DP值构造矩阵,这个复杂度是$O(n+n \log n)$,相比而言常数更优秀,不过代码更长,考虑到这个题没卡常,就懒得写了233
总复杂度是$O((n+m) \log^2 n)$。

代码

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

#include <algorithm>
#include <vector>

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();
    for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
    for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
    return res * neg;
}

const int MAXN = 100005;

struct Matrix {
    LL m[2][2];
    Matrix() {
        memset(m, 0, sizeof(m));
    }
    inline LL* operator[](const int &x) const {
        return const_cast<LL*>(m[x]);
    }
    inline Matrix operator*(const Matrix &rhs) const {
        Matrix res;
        for(int i = 0; i <= 1; i++) {
            for(int j = 0; j <= 1; j++) {
                for(int k = 0; k <= 1; k++) {
                    res[i][j] = std::max(res[i][j], m[i][k] + rhs[k][j]);
                }
            }
        }
        return res;
    }
    inline Matrix& operator*=(Matrix &rhs) {
        return *this = *this * rhs;
    }
};

int n, m, w[MAXN];
std::vector<int> gra[MAXN];

int fa[MAXN], siz[MAXN], dep[MAXN], son[MAXN], top[MAXN], end[MAXN], dfn[MAXN], vn[MAXN], clk;

void dfs1(int u) {
    siz[u] = 1;
    for(auto v : gra[u]) {
        if(v == fa[u]) continue;
        fa[v] = u; dep[v] = dep[u] + 1;
        dfs1(v);
        if(siz[v] > siz[son[u]]) son[u] = v;
    }
}

void dfs2(int u, int tp) {
    dfn[u] = ++clk; vn[dfn[u]] = u; top[u] = tp; 
    end[tp] = std::max(end[tp], dfn[u]);
    if(son[u]) {
        dfs2(son[u], tp);
    }
    for(auto v : gra[u]) {
        if(v == fa[u] || v == son[u]) continue;
        dfs2(v, v);
    }
}

Matrix tr[MAXN << 2], val[MAXN];

#define lch o << 1
#define rch o << 1 | 1
#define mid ((l + r) >> 1)

void modify(int o, int l, int r, int x) {
    if(l == r) {
        tr[o] = val[vn[l]]; return;
    }
    if(x <= mid) modify(lch, l, mid, x);
    else modify(rch, mid + 1, r, x);
    tr[o] = tr[lch] * tr[rch];
}

Matrix query(int o, int l, int r, int ll, int rr) {
    if(l == ll && r == rr) return tr[o];
    if(rr <= mid) return query(lch, l, mid, ll, rr);
    else if(ll > mid) return query(rch, mid + 1, r, ll, rr);
    else return query(lch, l, mid, ll, mid) * query(rch, mid + 1, r, mid + 1, rr);
}

inline Matrix query(int u) {
    return query(1, 1, n, dfn[top[u]], end[top[u]]);
}

inline void modify(int u, int v) {
    val[u][1][0] += v - w[u];
    w[u] = v;
    Matrix m1, m2;
    while(u) {
        m1 = query(top[u]);
        modify(1, 1, n, dfn[u]);
        m2 = query(top[u]);
        u = fa[top[u]];
        val[u][0][0] += std::max(m2[0][0], m2[1][0]) - std::max(m1[0][0], m1[1][0]);
        val[u][0][1] = val[u][0][0];
        val[u][1][0] += m2[0][0] - m1[0][0];
    }
}

int main() {
    n = readint(); m = readint();
    for(int i = 1; i <= n; i++) {
        w[i] = readint();
    }
    for(int i = 1, u, v; i < n; i++) {
        u = readint(); v = readint();
        gra[u].push_back(v); gra[v].push_back(u);
    }
    dfs1(1);
    dfs2(1, 1);
    for(int i = 1; i <= n; i++) {
        int t = w[i]; w[i] = 0;
        modify(i, t);
    }
    while(m--) {
        int x = readint(), y = readint();
        modify(x, y);
        Matrix res = query(1);
        printf("%lld\n", std::max(res[0][0], res[1][0]));
    }
    return 0;
}
[HAOI2008]硬币购物 题解

[HAOI2008]硬币购物 题解

题目地址:洛谷:【P1450】[HAOI2008]硬币购物 – 洛谷、BZOJ:Problem 1042. — [HAOI2008]硬币购物

题目描述

硬币购物一共有4种硬币。面值分别为c1,c2,c3,c4。某人去商店买东西,去了tot次。每次带di枚ci硬币,买si的价值的东西。请问每次有多少种付款方法。

输入输出格式

输入格式:
第一行 c1,c2,c3,c4,tot 下面tot行 d1,d2,d3,d4,s

输出格式:
每次的方法数

输入输出样例

输入样例#1:

1 2 5 10 2
3 2 3 1 10
1000 2 2 2 900

输出样例#1:

4
27

说明

di,s<=100000
tot<=1000
[HAOI2008]

题解

每次询问对着跑一遍背包并不现实,所以我们换一种思路。我们预处理不带硬币限制的背包方案数,并且考虑一个容斥的思路。假如不带限制的答案是$dp[s]$,第$i$种硬币限制了$d_i$个,那么从$d_i+1$往后的所有方案都是不可行的,因此要从不带限制的答案中减去一个$dp[s – c_i(d_i+1)]$。这样的话,会减重两个硬币都超了的方案,因此要加上$dp[s – c_i(d_i+1) – c_j(d_j+1)]$,以此类推。
复杂度$O(4 \times 100000 + tot \cdot 2^4)$。

代码

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

#include <algorithm>

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();
    for(; !isdigit(c); c = fgc()) if(c == '-') neg = -1;
    for(; isdigit(c); c = fgc()) res = (res << 1) + (res << 3) + c - '0';
    return res * neg;
}

const int MAXN = 100005;

int c[5], d[5], tot, s, cnt[1 << 4 + 5];
LL dp[MAXN];

int main() {
    for(int i = 1; i < 1 << 4; i++) {
        cnt[i] = cnt[i >> 1] + (i & 1);
    }
    for(int i = 1; i <= 4; i++) {
        c[i] = readint();
    }
    dp[0] = 1;
    for(int i = 1; i <= 4; i++) {
        for(int j = c[i]; j < MAXN; j++) {
            dp[j] += dp[j - c[i]];
        }
    }
    tot = readint();
    while(tot--) {
        for(int i = 1; i <= 4; i++) {
            d[i] = readint();
        }
        s = readint();
        LL ans = dp[s];
        for(int i = 1; i < 1 << 4; i++) {
            int neg = (cnt[i] & 1) ? -1 : 1;
            int res = 0;
            for(int j = 1; j <= 4; j++) {
                if(i & (1 << (j - 1))) {
                    res += c[j] * (d[j] + 1);
                }
            }
            if(s >= res) ans += neg * dp[s - res];
        }
        printf("%lld\n", ans);
    }
    return 0;
}