挑战!每天摄入 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\) 个菜的方案数,状态转移方程如下

\[\begin{cases} g[0][0] = 1 & basecase \\ g[i][j] = g[i-1][j] & j = 0 \\ g[i][j] = g[i-1][j] + g[i-1][j-1] \times s[i] & j \not= 0 \end{cases} \]

考虑如何求不符合条件的方案数,因为每行只能选择一个菜品,且不符合列有且只有一个,那么我们令 \(f[i][j][k]\) 表示前 \(i\) 行在第 \(col\) 列共选了 \(j\) 个菜品且在其他列 选择了 \(k\) 个菜品,状态转移方程如下

\[f[i][j][k] = f[i-1][j][k] + a[i][col] \times f[i-1][j-1][k] + (s_i - a[i][col]) \times f[i-1][j][k-1] \]

很不幸,时间复杂度为 \(\mathcal{O(mn^3)}\),超时。

但是你会发现我们关心的只是 \(j\)\(k\) 的相对大小,所以我们令 \(f[i][j]\) 表示:前 \(i\) 行,当前列 \(col\) 所选的数比其他列所选的数多了 \(j\) 个,状态转移方程如下:

\[\begin{cases} f[0][0] = 1 & basecase \\ f[i][j] = f[i-1][j] + f[i-1][j-1] \times a[i][col] + f[i-1][j+1] \times (s_i - a[i][col]) \\ \end{cases} \]

\(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难了!

posted @ 2022-08-21 10:18  LittleMoMol  阅读(22)  评论(0编辑  收藏  举报
*/