[SDOI2010]古代猪文 题解
题目地址:ė …
May all the beauty be blessed.
题目地址:洛谷:【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;
}
题目地址:洛谷:【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;
}
题目地址:洛谷:【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;
}