挑战!每天摄入 DP/SJJG 垃圾!
2022.8.21
每日 DP P2016 战略游戏
简单树形 \(DP\)
每日 DP P3147 [USACO16OPEN]262144 P
很奇怪的 \(DP\),令 \(f[i][j]\) 表示左端点为 \(j\),合并出 \(i\) 所到达的右端点的下一个点的位置,所以初始化时 \(f[x][i] = i + 1\)
状态转移方程:\(f[i][j] = f[i-1][f[i-1][j]]\)
感觉好玄学啊
2022.8.22
每日 DP P1095 [NOIP2007 普及组] 守望者的逃离
奇怪 \(DP\) 加一,我是这样理解的哈:等待的话,你啥时候等都一样,所以你进行双线工作:一条只去傻傻地跑,我们称其『傻子一号』;一条只去傻傻地回蓝闪现,我们称其『傻子二号』。那么重点来了!当『傻子二号』走过的距离比『傻子一号』多的时候,那么『傻子一号』将『傻子二号』的努力占为己有。
问题在于,这样做最后的答案是否是正确的?因为『傻子二号』能闪现就闪现,实在不行了才回蓝。而对于『傻子一号』,当他走的距离一旦小于二号的话,就直接更新,由于『闪现/回蓝』不受时间限制,所以这说明你耗费当前『傻子二号』所耗费的精力去『闪现/回蓝』是更优的且必须的,保证算法正确性。
每日 SJJG P2894 [USACO08FEB]Hotel G
线段树进阶!给 0/1 序列,维护区间最大的连续 0 的长度,存储 \(ltot,rtot,sum,len\) 再进行及其#¥(*……的操作进行更新即可
void pushup(int idx)
{
if (tr[idx << 1].len == tr[idx << 1].sum)
tr[idx].ltot = tr[idx << 1].len + tr[idx << 1 | 1].ltot;
else tr[idx].ltot = tr[idx << 1].ltot;
if (tr[idx << 1 | 1].len == tr[idx << 1 | 1].sum)
tr[idx].rtot = tr[idx << 1 | 1].len + tr[idx << 1].rtot;
else tr[idx].rtot = tr[idx << 1 | 1].rtot;
int d1 = tr[idx << 1].sum;
int d2 = tr[idx << 1 | 1].sum;
int d3 = tr[idx << 1].rtot + tr[idx << 1 | 1].ltot;
tr[idx].sum = max(max(d1, d2), d3);
return;
}
void cover_eval(warma &t, int co)
{
if (co == 1)
{
t.ltot = t.rtot = t.sum = 0;
t.cov = 1;
}
else
{
t.sum = t.ltot = t.rtot = t.len;
t.cov = 0;
}
return;
}
自己慢慢看叭
2022.8.23
每日 SJJG P4949 最短距离
问题就在于多出来的这一条边,我们用并查集来判断这条边的权值以及左右端点,这些想到了就不难了。
但是这个毒瘤代码真的是(已被和谐),犯了一个致命错误,多出的那条边是不会连在原图中的,但是我修改的时候默认它连上了,又因为数据非常水,得了 \(70pts\),导致我调了半天才过……
每日 DP P1156 垃圾陷阱
奇奇怪怪的状态定义,\(dp[i][j]\) 表示第 \(i\) 个垃圾高度为 \(j\) 时的最大生命值,然后就莫名其妙的推出来了,总之还是我太蒟了……
2022.8.24
每日 DP P3174 [HAOI2009] 毛毛虫
调了一下午……脑瘫了……
其实就是求以根节点为顶点的最长链和次长链,只不过这里定义链的长度为:链节点和每一个链节点的子节点之和。
状态转移方程:\(f[u] = \max _{v \in son_u}f_v + 1 + \max(0, cnt - 1)\),但是细节有点多,叶子节点和根节点的状态转移方程会有微调……一下午没啦……
2022.8.25
每日 DP P1273 有线电视网
做了俩小时……我的思路太乱了……
重新理清,令 \(dp[i][j]\) 为以 \(i\) 为根节点的子树中选择 \(j\) 个用户让他们可以看到电视的最大利润(可能为负)。
我太累了不会写思路……上核心代码吧呜呜呜
int dfs(int u)
{
if (u > n - m)
{
dp[u][1] = val[u];
return 1;
}
int sum = 0;
for (int i = h[u]; ~i; i = ne[i]) //枚举组
{
int ver = e[i];
int t = dfs(ver);
sum += t; //sum为以u为根节点的用户数量
for (int j = sum; j > 0; j -- ) //枚举背包容量
{
for (int k = 1; k <= t; k ++ ) //枚举物品选择
{
if (j - k >= 0) dp[u][j] = max(dp[u][j], dp[u][j - k] + dp[ver][k] - w[i]);
}
}
}
return sum;
}
每日 DP P2014 [CTSC1997] 选课
树形背包模板,但我不会,咕……
每日 DP P1122 最大子树和
这个不难,还是可以理解的
2022.8.26
没写!咕!
2022.8.27
每日 SJJG P3792 由乃与大母神原型和偶像崇拜
题目很毒瘤,很长但是有用的就两行……我刚开始看以为前面的背景都和题目息息相关呢……呀哒!
简单说询问一段区间问能否排列成连续的递增一的整数段,如 \(5,2,4,3 \rightarrow 2,3,4,5\),允许单点修改。
解法:用线段树维护区间最小值、区间最大值、区间和、区间平方和、区间立方和,然后玄学搞一搞,再花上俩小时调一调就过了,很 \(NB\),我也就调了 \(2.5h\)。
每日 SJJG P2596 [ZJOI2006]书架
很经典的平衡树题目,挺痛苦的也挺晕的,平衡树的中序遍历为书层 \(1 \sim n\),存的值是对应的书的编号,然后……码!
每日 DP P5322 [BJOI2019] 排兵布阵
其实是简单的 \(0/1\) 背包,只是不容易发现。
因为你的决策是固定的,并且是 \(1V1\ PK\),那么你令 \(a[i][j]\) 为第 \(i\) 个城堡中发兵数量第 \(j\) 小的兵数(事先排序),对于每一个城堡 \(i\),你枚举 \(0 \le j \le s\),把每一个 \(j\) 所对应的 \(a[i][j]\) 当作一个物品,它的价值为 \(ij\),它的重量为 \(a[i][j] \times 2 + 1\)。所以我们枚举城堡 \(i\),对于每一个城堡倒序枚举
我方打算出兵的数量 \(j\),在该数量上,枚举每一个对手 \(k\) 的决策,如果 \(j > 2 \times a[i][k]\),那么状态转移。
核心代码如下
for (int i = 1; i <= n; i ++ )
for (int j = m; j >= 0; j -- )
for (int k = 1; k <= s; k ++ )
if (j - a[i][k] * 2 - 1 >= 0)
dp[j] = max(dp[j], dp[j - a[i][k] * 2 - 1] + i * k);
每日 DP CF730J Bottles
感觉挺难的,看题解看不太懂
第一问很简单不说。
第二问不会,暂时不说。
2022.8.28
周日,玩!(理直气壮.jpg
2022.8.29
咕……
2022.8.30
每日 DP P5664 [CSP-S2019] Emiya 家今天的饭
说实话,这个想法真的很妙!
你会发现“主要食材至多在一半的菜中被选中”这个条件只可能发生在一个食材上面,也就是说只可能有一列不符合条件。那么这里应用一个简单的容斥,我们用总方案数-不符合条件的方案数,不就是答案吗?
前置:令 \(s_i\) 为第 \(i\) 行的总菜数
考虑如何求总方案数,令 \(g[i][j]\) 表示前 \(i\) 行选择 \(j\) 个菜的方案数,状态转移方程如下
考虑如何求不符合条件的方案数,因为每行只能选择一个菜品,且不符合列有且只有一个,那么我们令 \(f[i][j][k]\) 表示前 \(i\) 行在第 \(col\) 列共选了 \(j\) 个菜品且在其他列 选择了 \(k\) 个菜品,状态转移方程如下
很不幸,时间复杂度为 \(\mathcal{O(mn^3)}\),超时。
但是你会发现我们关心的只是 \(j\) 和 \(k\) 的相对大小,所以我们令 \(f[i][j]\) 表示:前 \(i\) 行,当前列 \(col\) 所选的数比其他列所选的数多了 \(j\) 个,状态转移方程如下:
\(j\) 可能为负数,所以 \(f[i][n+|j|]\) 我们表示多了 \(j\) 个;\(f[i][n-|j|]\) 我们表示少了 \(j\) 个。
#include <iostream>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 110, M = 2010;
const LL mod = 998244353;
int n, m, a[N][M], s[N]; //s[i] 第i行可选总数
LL f[N][N * 2], g[N][N];
//f[i][j] 前i行,当前列的数比其他列多j个的方案数
//g[i][j] 前i行共选了j个数的方案数
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
{
cin >> a[i][j];
s[i] = (s[i] + a[i][j]) % mod;
}
LL ans = 0;
for (int col = 1; col <= m; col ++ )
{
memset(f, 0, sizeof f);
f[0][n] = 1;
for (int i = 1; i <= n; i ++ )
for (int j = n - i; j <= n + i; j ++ )
f[i][j] = (f[i - 1][j] + f[i - 1][j - 1] * a[i][col] % mod + f[i - 1][j + 1] * ((s[i] - a[i][col] + mod) % mod)) % mod;
for (int j = 1; j <= n; j ++ )
ans = (ans + f[n][n + j]) % mod;
}
g[0][0] = 1;
for (int i = 1; i <= n; i ++ )
for (int j = 0; j <= n; j ++ )
g[i][j] = (g[i - 1][j] + (j > 0 ? (g[i - 1][j - 1] * s[i] % mod) : 0)) % mod;
LL sum = 0;
for (int i = 1; i <= n; i ++ ) sum = (sum + g[n][i]) % mod;
ans = ((sum - ans) % mod + mod) % mod;
cout << ans << endl;
return 0;
}
每日 SJJG P1937 [USACO10MAR]Barn Allocation G
说是数据结构,不如说是贪心。
数据结构部分就一个简单的线段树维护区间最小值,但是贪心证明我不会……
因为贪心证明我不会所以在这里我不说……看看过几年我能补上这个坑
每日 DP T263092 gift
模拟赛的 \(T4\),说是 \(01\) 背包求方案数,但我感觉挺懵的,先放上代码以后再填坑……
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const LL mod = 1e7 + 7;
const int N = 1010;
int n, m;
LL a[N], s[N], ans, f[N];
int main()
{
cin >> n >> m;
LL tot = 0;
for (int i = 1; i <= n; i ++ )
{
cin >> a[i];
tot += a[i];
}
if (m >= tot)
{
cout << 1 << endl;
return 0;
}
sort(a + 1, a + 1 + n);
for (int i = 1; i <= n; i ++ ) s[i] = s[i - 1] + a[i];
f[0] = 1;
for (int i = n; i >= 1; i -- )
{
for (int j = m - s[i - 1]; j >= max(0ll, m - s[i - 1] - (a[i] - 1)); j -- )
ans = (ans + f[j]) % mod;
for (int j = m; j >= a[i]; j -- )
f[j] = (f[j] + f[j - a[i]]) % mod;
}
cout << ans << endl;
return 0;
}
2022.8.31
每日 SJJG P1503 鬼子进村
方法很多,我用的平衡树,就此总结了一下平衡树的各种操作,不过还不全。
2022.9.1
每日 DP P3957 [NOIP2017 普及组] 跳房子
虽说这是个绿题,但是涉及到二分+单调队列优化的DP,感觉有点超纲。
这个单调队列优化我不太认识,和我想象中的不一样,导致很难理解……
2022.9.2
每日 DP P3112 [USACO14DEC]Guard Mark G
状态压缩 \(DP\),比较难,毕竟我还没怎么做过这类的题……说是有种贪心的做法,但是证明过于神犇,本蒟蒻不会……
每日 SJJG P2286 [HNOI2004]宠物收养场
蛮有意思的一道 \(Splay\) 题目,一颗树代表两种性质,并且涉及到了求一个平衡树中不存在的值的前驱后继操作,详见博客。
2022.9.3
每日 SJJG C 鬼鬼与石子
\(51nod\) 的题,感觉质量不行,这个题是树链剖分+\(Nim\) 游戏板子,还可以。
\(51nod\) 大锑!!
2022.9.4
咕
2022.9.5
每日 DP P2602 [ZJOI2010] 数字计数
数位 DP 杀我!树形结构就不说了,说说预处理以及计算
预处理
令 \(f[i][j][u]\) 表示有 \(i\) 位且最高位的数字为 \(j\) 的所有数中的出现次数。
Base case:\(f[1][i][i] = 1,\ i \in [0,9]\)
状态转移方程:\(f[i][j][u] = \begin{cases} f[i-1][k][u],\ k \in [0,9] & j \not= u \\ 10^{i-1} + f[i-1][k][u],\ k \in [0,9] & j = u \end{cases}\)
计算
- 令 \(\operatorname{dp(n,\ u)}\) 表示 \(0 \sim n\) 这些数中 \(u\) 这个数字出现的个数,\(u \in\ [0,9]\)
- 令 \(ans\) 为返回值,\(last\) 记录之前位数出现 \(u\) 的总数(后面会解释)
- 令 \(n = \overline{a_{k-1} a_{k-2}\cdots a_1 a_0},\ k\) 为 \(n\) 的位数。
先考虑恰好为 \(k\) 位数对答案的贡献,因为这个是最难的……那么最高位最小为 \(1\),其他位最小为 \(0\)。
WOC!我不写了,傻X数位!太TM难了!