一些 dp 题
笑点解析:dp 题单还是我暑假拉的。
CF1989E
有一个任意构造的 \(a\) 序列,需要保证 \([1,k]\) 中每个数至少出现一次,\(b_i\) 定义为距离 \(a_i\) 最近不同元素的距离,求不同 \(b\) 的个数,对 \(998244353\) 取模。
\(2\leq n \leq 2\times 10^5,2\leq k \leq \min(n,10)\)
想一半看了题解。。。
首先 \(a\) 的元素具体是什么肯定不重要,我们只关心它在 \(b\) 中的分段情况,每一个分块,可以看做是一个先上升后下降的等差数列,形似 \(1,2,\dots,\frac{L}{2},\dots,2,1\),这样就是这样的段拼接在一起形成 \(b\) 序列,转移很简单,不考虑段数 \(f(i) = \sum \limits_{j=1}^{i-1} f(j)\),这个显然可以前缀和优化。但是考虑到一个长度为 \(2\) 和两个长度为 \(1\) 的段同样都是 \(1~1\),所以每次前缀和之后需要减去重复方案。
到这里,我们还没有考虑分成至少 \(k\) 段的要求,考虑到这个 \(k\) 很小,所以我们用全部答案减去小于 \(k\) 的答案就好了,对于小于 \(k\) 的求解,我们令 \(g_{i,j}\) 表示到第 \(i\) 个位置,已经分了 \(j\) 段的方案数,\(pre_{i,j} = \sum \limits_{k=1}^{i-1} g_{k,j}\) 做一下前缀和优化,然后每次再减一下重复情况就没了。
复杂度 \(O(nk)\)。
CF833B
将 \(n\) 个数划分为恰好 \(k\) 段,每段的价值定义为段内不同数个数,求价值最大值。
\(1 \leq k \leq 50,1\leq n \leq 3.5\times 10^5\)
算是切了吧,代码实现参考了一下题解。
设计 \(f_{i,j}\) 表示前 \(i\) 个位置划分为 \(j\) 段的最大权,显然有转移 \(f_{i,k} = \max \left \{ f_{j-1,k-1} + \sum \limits_{p=j}^i [pre_{a_p} < j]\right \}\),其中 \(pre_{a_i}\) 表示 \(a_i\) 上一次的出现位置,\(\max\) 中后半部分的计数就是 \([i,j]\) 区间的贡献,这样转移是 \(O(n^2k)\) 的,显然无法通过,考虑优化。
我这里还多拆了一步,把后面拆成前缀和形式其实会更好理解,但也可以不拆,我们考虑到 \(i\) 位置在 \(i-1\) 的答案对前面的答案进行添加,发现后半部分式子当且仅当 \(pre_{a_i} < j \leq i\) 时,里面的 \(j\) 会产生 \(1\) 的贡献,做一下下标位移,我们直接对 \([pre_{a_i},i-1]\) 区间的答案区间加 \(1\) 就好了,发现这个东西可以用线段树维护,所以扔上线段树,复杂度 \(O(nk\log n)\)。
具体实现时,可以先枚举分割段数,每次重构线段树,代码实现相对更加简单。
P1415
给定一个数字串,在某些两个数字之间添加逗号,使形成的数列严格单调上升,并且字典序最大。
\(1 \leq l \leq 500\)
模拟赛因为 T3 唐诗结论没调出来,所以没读题。。。。
问题转化一下,显然是要求每一个位置的最靠后的分割位置,直接 dp 所暴露出的问题就是不符合严格单调上升的东西,dp 出的序列到最后不合法。所以我们需要先求出 最小起始分割位置 保证在这个位置之后划分的位置后面都是可以合法的。
我们设 \(f_i\) 表示前 \(i\) 个数拆分,最后一个数的最小划分位置为 \(f_i\),那么有转移 \(f_i = \max \left \{ j [num(f_{j-1},j-1) < num(j,i)]\right \}\),特别地,我们令 \(f_1 = 1\),这样,我们就有了 \(f_i\)。
然后我们设 \(dp_i\) 表示后 \(n-i+1\) 个数拆分,第一个数的最大划分位置为 \(dp_i\),那么我们同样有转移 \(dp_i = \max \limits_{i=1}^{f_n-1} \left \{ j [num(i,j) < num(j+1,dp_{j+1})]\right \}\),特别地,有 \(dp_{f_n} =n\)。
然而这样还没有做完,我们考虑一个例子类似 1234050,正确的划分应该为 12,34,050,而我们并没有考虑最后 050 的划分,所以我们要对最后一个数的前导零做特殊处理,即令其 \(dp_i = n\)。
复杂度 \(O(n^3)\)。
P9871
给定 \(m\) 个得分区间,区间全部覆盖才能得分,不能连续覆盖超过 \(k\) 个位置,一共有 \(n\) 个位置,求得分最大值。
\(1 \leq n\leq 10^9\),\(1 \leq m,k \leq 10^5\)
看过好几遍了,就是线段树优化 dp 的板子吧。
考虑 \(f_i\) 表示前 \(i\) 个位置的最大得分,显然有 \(f_i = \max (f_i,f_{i-1})\),然后考虑暴力枚举转移左端点 \(j\),对于 \(j \geq i-k+1\),有转移 \(f_i = \max \left \{ f_{j-2}-(i-j+1)\times d+w_{j,i} \right \}\),对于 \(j \leq i-k+1\),我们在第一个转移中已经进行了。
这样转移是 \(O(n^2k)\),由于 \(n\leq 10^9\),每次能够产生贡献的也只有区间左右端点,所以很自然离散化,复杂度变为 \(O(m^2k)\),用线段树或者树状数组可以很简单地维护 \(w_{j,i}\),具体地,线段树上每一个叶节点 \(j\) 表示从 \(j\) 点开始走到当前节点带来的贡献,显然每一次对 \([1,l_i]\) 区间加法就好了,此时复杂度为 \(O(mk\log m)\),那么我们的转移就是对 \(j\in [i,i-k+1]\) 做转移,这个东西显然可以拆开扔上线段树做区间 \(\max\),更加具体地,我们把与 \(i\) 有关的分离出来,转移变成 \(f_i = -(i+1)\times d + \max \left \{ f_{j-2}+j\times d + w_{j,i}\right \}\),拿线段树维护后面式子就好了,就把刚才计算 \(w_{j,i}\) 的线段树上的 \(i\) 每次再加上 \(f_{i-2} + i \times d\) 即可。
复杂度 \(O(m\log m)\),可以通过。
CF2066C
给定一个序列 \(\left \{ a \right \}\),每次需要对 \(P,Q,R\) 三个变量中的一个做 \(\oplus a_i\) 操作,要求任意时刻 \(P,Q,R\) 至少有两个数值相等,求操作方案数。
\(n \leq 2\times 10^5\)
分析性质,令 \(p_i\) 表示 \(a_i\) 的前缀异或和,则任意时刻有 \(P \oplus Q \oplus R =p_i\),根据这个性质,每一个 \(PQR\) 数对都能被表示为 \((p_i,x,x)\) 的样子,设 \(f_{i,x}\) 表示第 \(i\) 个位置相同位置的取值为 \(x\)。
考虑转移,数对 \((p_i,x,x)\) 一定会由 \((p_i \oplus a_i,x,x)\) 或 \((p_i,x\oplus a_i,x)\) 转移而来,发现第一个其实就是数对 \((p_{i-1},x,x)\),也就是 \(f_{i-1,x}\),所以有转移 \(f_{i,x} \gets f_{i-1,x}\),对于第二个数对,我们分类讨论,因为上一个数对也要有两个相同数,所以 \(x\) 只能为 \(p_{i-1}\) 或 \(p_i\),\(p_i\) 的方案已经算过了,考虑 \(x=p_{i-1}\) 的方案数,那么上一个数对一定是形似 \((p_{i-1},p_{i-1},p_{i-1})\) 或 \((p_{i-1},p_i,p_{i-1})\) 的,对于第一个,有 \(3\) 种方法,对于第二个,有两种方法,所以有转移 \(f_{i,p_{i-1}} = f_{i-1,p_{i-1}} \times 3 + f_{i-1,p_i} \times 2\)。滚动数组压一下用 map 存就好了。
AGC033D
给定一个 \(n\times m\) 的 01 矩阵,定义如果一个矩阵全是 \(0\) 或 \(1\),那么代价为 \(0\),否则答案为选一行或列分割后,两部分的代价 \(\max +1\)。
\(n,m \leq 185\),时限 5s,空间 512MB。
好题。
首先有一个显然的做法,设计 \(f_{i,j,x,y}\) 表示左上角是 \((i,j)\) 右下角是 \((x,y)\) 的最小答案,然后可以枚举矩形边长和分割线做到 \(O(n^5)\) 的复杂度,无法通过。
考虑优化以上做法,一个最劣的情况是所有位置周围的数字全部与当前位置不同,那么我们是每两个数字一合并最优,这只会合并 \(\log (nm)\) 轮,所以答案的上界只有 \(\log (nm)\)。
发现了这个性质之后,我们就可以优化上述做法了,在一个大矩形枚举的时候,因为可能最优的转移位置只有 \(\log\) 个,所以枚举分割线可以省去,复杂度是 \(O(n^4\log n)\) 的,但是时间优化了空间不够。
再换一个想法,要优化空间就把答案扔到状态上,重新改一下状态,\(f_{k,u,l,r}\) 表示第 \(u\) 行及其往下,\([l,r]\) 这个范围内合并不超过 \(k\) 次所能到达的最大行数是多少。
这个状态就很巧了,对于答案的判定,我们枚举答案,然后看 \(f_{k,1,1,m}\) 是否能扩展到 \(n\) 即可。
由 \(k=0\) 开始看,我们首先要预处理出这个东西,考虑直接枚举 \(u,l\),然后从 \(l\) 开始枚举 \(r\),因为每次向右走的时候最大扩展位置一定单调不增,所以额外开一个变量每次减就好了,复杂度是 \(O(n^3)\),而且卡不满。
然后是转移,首先有 \(f_{k,u,l,r} \gets f_{k-1,u,l,r}\),然后考虑枚举行来分割的情况,最优的显然是这一部分 \(k-1\) 答案的下一行也拿 \(k-1\) 来扩展,写出来就是 \(f_{k,u,l,r} \gets f_{k-1,f_{k-1,u,l,r}+1,l,r}\)。
对于枚举列来分割的情况,我们还是先枚举分割点 \(x\),那么最远扩展位置是 \(f_{k,u,l,r} \gets \min(f_{k-1,u,l,x},f_{k-1,u,x+1,r})\),之后对以上说的转移取 \(\max\) 就做完了。
时间复杂度 \(O(n^4 \log n)\),空间 \(O(n^3\log n)\),可以通过,可能需要卡卡常,我跑的还挺快的。
P10119
一个长 \(N\) 序列,每个点是一个二元组 \((a_i,b_i)\),从头开始,选择初始状态 \(0/1\),在 \(0\) 获得 \(a_i\),在 \(1\) 获得 \(b_i\),每次向右可以变换状态,变换次数不超过 \(K\),如果一次变换与上次变换的距离在 \(T\) 以内,产生 \(P\) 的贡献,每次是先选择是否换边再加 \(a_i/b_i\),在 \(1\) 处不可换边,但可以选初始状态,求最终最大贡献值。
\(1 \leq N \leq 2\times 10^5, 1 \leq K \leq \min \left \{200,N \right \}, 1 \leq T \leq {2\times 10^4,N},|a_i|,|b_i|,|P| \leq 10^9, NK \leq 2\times 10^7\)。
题面好长,感觉题意概括的跟原题面一样长了,题解直接从洛谷搬的,建议看原题题面。
考虑 dp,令 \(f_{i,j,0/1}\) 表示在第 \(i\) 分钟末,恰好进行了 \(j\) 次踱步,此时在内/外的最大心情值,也就是必须在第 \(i\) 分钟初进行一次踱步。
这个状态的设计主要是为了方便确定贡献区间的范围,如果将 \(j\) 的意义改为已经进行了 \(j\) 次,那么我们将无法找到上一次具体的踱步位置。
令 \(pre_{i,0/1}\) 分别表示内/外的前缀和,可以写出转移:
可以将与 \(x\) 无关的项提出来,然后分类讨论:
- 
对于 \(x < i-T\),转移是 \(f_{i,j,op} = a_{i,op} + pre_{i-1,1-op} +\max_{x=j-1}^{i-1} f_{x,j-1,1-op} - pre_{x,1-op}\),我们顺次枚举 \(i\),那么后面的 \(\max\) 用一个变量每次维护就好了。 
- 
对于 \(x \geq i-T\),转移与上一种情况只是多了一个 \(P\),而对于 \(\max\),我们要维护 \(i\) 之前 \(T\) 个位置的 \(\max\),这个单调队列就可以做到。 
注意要预处理 \(j=0,1\) 的情况,此时空间复杂度是 \(O(nk)\) 的,无法通过。发现 \(T \leq 2\times 10 ^4\),所以对于前缀 \(\max\) 的维护只需要维护 \(T\) 个就行,改成这样滚动就可以通过了。
时间复杂度 \(O(nk)\),空间 \(O(Tk)\)。
P6879
一个环上,给出每个点到原点的距离 \(a_i\),和环长 \(L\),每个点有最晚到达时刻 \(t_i\),每一时刻可以向左右走一单位长度,求在到达时刻以内到达的最多点数。
\(1 \leq n \leq 200,1 \leq L \leq 10^9\)。
扩展类的区间 dp,考虑设状态 \(f_{i,j,k}\) 表示向左走最多 \(i\) 个,向右走最多 \(j\) 个,用时 \(k\) 的最多达到数,发现空间开不下,于是考虑交换状态,把答案放到状态上,也就是把 \(k\) 变成到达数为 \(k\) 时的最小时间。
转移是简单的,可以对照代码,每次向左右拓展倒是可以当做很典型的一种区间 dp 写法:
	fo(0,l,n) fo(0,r,n-l-1) fo(0,k,n)
	{
		ll num=f[l][r][k][0];
		if(num!=INF)
		{
			ll op=(num+a[n-l+1]-a[n-l]<=t[n-l]);
			f[l+1][r][k+op][0]=min(f[l+1][r][k+op][0],num+a[n-l+1]-a[n-l]);
			op=(num+L-a[n-l+1]+a[r+1]<=t[r+1]);
			f[l][r+1][k+op][1]=min(f[l][r+1][k+op][1],num+L-a[n-l+1]+a[r+1]);
		}
		num=f[l][r][k][1];
		if(num!=INF)
		{
			ll op=(num+a[r]+L-a[n-l]<=t[n-l]);
			f[l+1][r][k+op][0]=min(f[l+1][r][k+op][0],num+a[r]+L-a[n-l]);
			op=(num+a[r+1]-a[r]<=t[r+1]);
			f[l][r+1][k+op][1]=min(f[l][r+1][k+op][1],num+a[r+1]-a[r]);
		}
	}
Black Nodes in Subgraphs
多测,一棵树每个点有颜色,\(q\) 次询问,每次给定 \(s,d\),询问能否找到大小恰好为 \(s\) 的连通子图恰好有 \(d\) 个黑点。
\(T \leq 5,n \leq 5000,q \leq 10^5,0 \leq d \leq s,1 \leq s \leq n\)。
codechef 题,放的是 vj 链接。
首先有一个 \(O(\frac{Tn^3}{w})\) 的 bitset 做法,令 \(f_{u,i,j}\) 表示以 \(u\) 为根,且这个根必选的恰好 \(i\) 个点能否有 \(j\) 个黑点,转移显然是 \(O(n^2)\),再用 bitset,但是这个复杂度并不能通过。
让我们重新考虑,讲判定性问题转化为最值问题,令 \(f_{i,s}\) 表示以 \(i\) 为根的子树内,选 \(s\) 个点的最多黑点个数,同理 \(g_{i,s}\) 表示最少黑点个数,可以通过简单调整发现可取的黑点个数一定是 \([g_{i,s},f_{i,s}]\),然后可以拿 bit 维护一下区间,每次查询就可以做到 \(O(Tn^2\log n)\) 的复杂度。
CF1312E
一个数列,每次可以合并相邻两个相同项,设这两项的值为 \(x\),则用 \(x+1\) 替换之后放到原位置,求最终最小序列长度。
\(n \leq 10^5,a_i \leq 10^6\)。
显然区间 dp,直接 dp 一个区间的左右端点并不好做,不如转换一下,由于最终序列每个数字都不同,我们可以将其看作一段段数字,而且是连续的一段,所以我们直接令 \(f_{i,j}\) 表示区间 \([i,j]\) 中可以合并成一个数字 \(f_{i,j}\),然后直接 dp 就好了,遇到一个区间合并不完的,就不合法,不管了。
考虑下一步,此时我们只要用最少的段数去覆盖整个数列就好了,这个也是 dp,令 \(g_i\) 表示到 \(i\) 位置的最小步数,则有转移 \(f_i = \max_{j<i \wedge f_{j,i-1} \neq 0} f_j + 1\),直接做是 \(O(n^2)\) 的,完全可以通过,总复杂度 \(O(n^3)\)。
P11126
给定一个值域在 \([1,m]\) 的数组 \(a\),可以将三个相同数或三个连续数化为一组,求分组方案数。
\(1 \leq n,m \leq 5000\),\(n\) 是 \(3\) 的倍数。
显然是放到值域上 dp,由于一个点的方案只跟上一个和上上个有关,所以我们可以设计 \(f_{i,j,k}\) 表示第 \(i\) 个位置 \(i-1\) 剩了 \(j\) 个,\(k-1\) 剩了 \(k\) 个的方案数。
令 \(c_i\) 表示 \(i\) 的数量,把两个分组方式分开转移,我们钦定每一次的 \(k\) 不需要再进行自减,那么有三个数的分组方式 \(f_{i+1,c_i-k,j-k} \gets f_{i,j,k}\),自减的就只有 \(j\),发现这样也满足了前面的假定,\(f_{i,j-3,k} \gets f_{i,j,k}\),就基本做完了,这样复杂度是 \(O(m^3)\) 吗?分析一下,我们令 \(c'_i \gets \max(c_i,c_{i-1})\),那么 \(\sum c_i c_{i-1} \leq \sum {c'_i} ^2 \leq (\sum c'_i)^2 \leq 4m^2\),所以复杂度是正确的,\(O(m^2)\)。
P3287
求最多对 \(k\) 个区间 \(+1\) 的最长上升子序列。
\(1 \leq n \leq 10^4,1 \leq k \leq 500,1 \leq V \leq 5000\)。
死因是不会二维树状数组。
一个很显然的结论,每一次区间覆盖的右端点一定是 \(n\),考虑一下,每次都给后缀加一,那么两个后缀里的数所需的次数是不变的,而里面任意与前缀所需要的次数会减一,所以每次后缀加一是不劣的。
有了这个结论,直接 dp 就好了,但是我一开始不会二维树状数组,导致写了个 \(O(nk^2\log V)\) 的唐东西,改成二维树状数组就是 \(O(nk\log V^2)\) 的复杂度,可以通过。
二维树状数组:
struct BIT{
	ll bit[N][1005];
	#define lowbit(x) (x&(-x))
	inline void upd(ll i,ll y,ll k){for(;i<=V;i+=lowbit(i)) for(ll j=y;j<=1000;j+=lowbit(j)) bit[i][j]=max(bit[i][j],k);}
	inline ll ask(ll i,ll y){ll ans=0;for(;i;i-=lowbit(i)) for(ll j=y;j;j-=lowbit(j)) ans=max(ans,bit[i][j]);return ans;}
}bit;
CF1675G
有 \(n\) 组饼,每次可将一组饼内的某一个向左或向右移动,求使得序列单调不增的最小操作数。
\(1 \leq n \leq 250,1 \leq \sum a_i \leq 250\)。
好题,考虑设 \(f_{i,j,k}\) 表示前 \(i\) 个位置,放了 \(j\) 个饼,第 \(i\) 个位置放了 \(k\) 个饼的移动数。
发现转移只需要考虑相邻两个位置饼的移动,而这个移动是完全可以放到转移里面来搞的,所以我们有了第二维状态。
考虑转移,我们枚举第 \(i+1\) 个位置放了 \(p\) 个饼,那么 \(f_{i+1,j+p,p} = \min \{ f_{i,j,k} + |j+p-a_{i+1}|\}\),其中 \(a_i\) 表示前 \(i\) 组饼的数量,因为到 \(a_{i+1}\) 组,我们只让每一次饼向后推,所以此时 \(a_{i+1}\) 的值是恒定的,我们下一步只需要到 \(a_{i+2}\) 去借就好了,假如 \(j+p-a_{i+1} < 0\),则证明我们将 \(i\) 位置的饼向后放,否则就是将 \(i+2\) 的饼拿过来,因为是相邻位置移动,所以这样做一定是不劣的。
CF1716D
给定两个数 \(n,k\),问从 \(0\) 开始,第 \(i\) 步只能走 \(k+i-1\) 的正倍数(即不能走 \(0\)),问分别走 \(x \in [1,n]\) 的方案数,对 \(998244353\) 取模。
\(1 \leq k,n \leq 2\times 10^5\)。
简单题,不妨考虑一个点最多有 \(\sum i\) 的距离,那么距离就是 \(\frac{P(P-1)}{2}\),步数就是 \(P\),发现 \(P \leq 2\sqrt n\),我们直接根据这个 dp。
设 \(f_{i,j}\) 表示到第 \(i\) 个位置,走了 \(j\) 步的方案数,显然有转移 \(f_{x,j} = \sum f_{x-p(k+i-1),j}\),这个是 \(O(n^2 \sqrt n)\),考虑优化,发现转移的部分记作 \(y\) 的话,则 \(y \equiv x \pmod {k+i-1}\),按照取模后的值记前缀和即可,复杂度 \(O(n\sqrt n)\)。
可以滚动优化一下空间。
CF1666F
给定一个长 \(n\) 的不降序列,要求将其重排为 \(b_i < b_{i+1} > b_{i+2} < b_{i+3} > \dots\) 的新序列,偶数位置严格递增,求方案数。
\(1 \leq n \leq V \leq 5000\)。
好题。
回家路上想了半天,感觉差不多真了,回来一写发现看反题了。
由于答案序列状态与原序列无关,我们不妨将 dp 放到值域上来做,考虑设 \(f_{i,k}\) 表示到 \(i\),已经放了 \(k\) 个双层了,我们将一对 \(a_i < a_{i+1}\) 称为一层,记 \(p_i\) 表示 \(i\) 的个数,\(s_i\) 表示 \(\sum \limits_{j=1}^i p_i\),枚举 \(j\) 表示上一个放双层的值,那么有 \(f_{i,k} = \sum \limits_{j=1}^{i-1} f_{j,k-1} \times (s_{a_j-1} - 2(k-1)+1)\times p_i\),系数中间部分即去掉 \(\leq a_j\) 又加回来上一个选的 \(a_j\) 的选择,所以系数指 \(<a_j\) 之前还未被选择的位置,再乘上这一块数字的选择方案。
朴素转移是 \(O(n^3)\) 的,前缀和优化可以做到 \(O(n^2)\),注意最后方案数的去重,即把所有 \(p_i\) 带来的重复方案除掉。
P6799
给定树上 \(m\) 条链,求任意选择链使得链上的点无交的方案数。
\(1 \leq n,m \leq 2\times10^5\)。
思维好题。
先考虑一个子树怎么做,假设当前子树根 \(x\),有 \(y,z\) 两儿子,我们令 \(f_i\) 表示以 \(i\) 为根的答案,那么首先 \(y,z\) 的答案需要合并,其实就是直接乘,因为保证了左右两边的方案一定合法,但是这还没有算上新加入的点 \(x\) 带来的贡献,暂且将儿子的乘积记作 \(g_i\)。
一条链,我们将其贡献整个放到 \(lca\) 上来统计,对于当前一条 \(lca\) 在 \(x\) 的链,其左右端点 \(u,v\) 分别在 \(y,z\) 两子树内,记 \(P(x,y)\) 表示 \(x\) 到 \(y\) 路径上的点构成的点集,则其新带来的贡献为 \(\prod \limits_{i \in son_{P(y,x)} \wedge i \notin P(u,x)} f_i\),表示从 \(u\) 到 \(x\) 路径上所有点的子树答案乘积,且不包括链上的答案,因为需要链无交集,这只是一侧,另一侧同理,假设 \(x\) 还有另外几个子树,而 \(u,v\) 并不在那些子树内,则那些正常参与统计即可,也就是:
这一步转化其实很简单,考虑我们只要链挂着的那些子树,而不要链上的点,第二个式子分子其实是包括了链和子树的乘积,所以我们需要再分母除掉我们多算的 \(f_i\),这几步其实可以画画图可以更好理解。
推广到全树同理,之后就简单了,直接树剖维护 \(g_i\) 链积和 \(f_i\) 逆元链积就好了,注意判一下当 \(u,v\) 和 \(x\) 重合的情况,复杂度 \(O((n+m)\log^2 n)\),可以通过。
判了 \(1\) 就通过了 hack 数据,但感觉还是有点假,似乎也没有看懂 hack 的原理。
P4383
给定一个带负权的树,求选 \(k\) 条不交链的最大权值和。
\(1 \le n \le 3\times 10^5\),对于 \(60%\) 的数据,\(1 \le k \le 100\),对于全部数据,\(1 \le k \le 3\times 10^5\)。
好题,先考虑 \(60\) 分怎么搞。
发现我们的困难在于记录链不交,所以有一个很 naive 的想法,我们设 \(f_{0/1/2,i,j}\) 表示以 \(i\) 为根的子树内,\(i\) 在所属链上的度为 \(0/1/2\),已经选了 \(j\) 条链。
这个状态超级神仙了,然后分类讨论:
- 对于度数为 \(0\),那么其儿子也不能连上来,所以要么儿子不属于链,要么儿子是链的结尾,所以有转移:
- 对于度数为 \(1\),那么可能其儿子不属于链,或者其原来不属于链,但是儿子连上来了:
- 对于度数为 \(2\),那么要么其儿子不属于链,要么其是由两条链拼起来的:
写的好的树上背包可以做到 \(O(nk)\),注意一下边界情况的转移,这里给一个 \(O(nk^2)\) 的写法,也是第二篇题解的写法:
ll n,k,f[3][N][105];
vector<pii > g[N];
inline void dfs(ll x,ll fa)
{
	f[0][x][0]=f[1][x][0]=f[2][x][0]=0;
	for(pii vv:g[x])
	{
		ll y=vv.fi,w=vv.se;if(y==fa) continue;
		dfs(y,x);
        Fo(k,i,1)
    	{
        	// f[1][x][i]=max(f[1][x][i],f[0][x][i]+f[1][y][0]+w);
		    Fo(i-1,j,0)
		    {
		        f[0][x][i]=max(f[0][x][i],f[0][x][j]+f[0][y][i-j]);
		        f[1][x][i]=max(f[1][x][i],max(f[0][x][j]+f[1][y][i-j]+w,f[1][x][j]+f[0][y][i-j]));
		        f[2][x][i]=max(f[2][x][i],max(f[1][x][j]+f[1][y][i-j-1]+w,f[2][x][j]+f[0][y][i-j]));
		    }	
		}
		f[1][x][0]=max(f[1][x][0],f[0][x][0]+f[1][y][0]+w);
    }
	f[0][x][1]=max(f[0][x][1],0ll);
	fo(1,i,k) f[0][x][i]=max({f[0][x][i],f[1][x][i-1],f[2][x][i]});
}
int main()
{
	read(n),read(k);
	ll u,v,w;fo(1,i,n-1) read(u),read(v),read(w),g[u].pb({v,w}),g[v].pb({u,w});
	mem(f,-0x3f);k++;dfs(1,0);
    wr(max({f[0][1][k]})),pr;
	return 0;
}
然后进一步考虑 \(k \le 3\times 10^5\) 时的做法,简单观察这个转移,感觉是凸的,当选择的链数越多,那么每条边的变化量值就越大,主观感觉上是下凸的。
其实可以看看其他的题解什么的,反正之后我们就可以 wqs 二分这个东西,其实看到 恰好 k 个 也能猜个差不多。
注意我们需要对同时对 dp 数组维护两维信息,重定义一下就好了,复杂度 \(O(n \log n)\)。
#include<bits/stdc++.h>
#define fo(a,i,b) for(int i=(a);i<=(b);++i)
#define Fo(a,i,b) for(int i=(a);i>=(b);--i)
#define INF 0x3f3f3f3f3f3f3f3f
#define inf 0x3f3f3f3f
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define pr putchar('\n')
#define pp putchar(' ')
#define pii pair<ll,ll>
#define fi first
#define se second
using namespace std;
typedef long long ll;
inline void read(ll &opt){ll num=0,t=1;char ch=getchar();while(!(ch>='0'&&ch<='9')){if(ch=='-') t=-1;ch=getchar();}while(ch>='0'&&ch<='9') num=num*10+(ch-'0'),ch=getchar();return opt=num*t,void();}
inline void wr(ll x){if(x<0) putchar('-'),x=-x;if(x>=10) wr(x/10);putchar(x%10+'0');}
const ll N=3e5+5,M=2e6+5;
ll n,k,tmp[3][105];
vector<pii > g[N];
struct data
{
	ll x,y;
	inline bool operator < (const data &b)const{return b.x==x?y>b.y:x<b.x;}
	inline data operator + (const data &b)const{return (data){x+b.x,y+b.y};}
	inline data operator + (ll b){return (data){x+b,y};}
}f[3][N];
inline data delt(data a,ll k){return (data){a.x-k,a.y+1};}
inline void dfs(ll x,ll fa,ll mid)
{
	f[2][x]=max(f[2][x],(data){-mid,1});
	for(pii vv:g[x])
	{
		ll y=vv.fi,w=vv.se;if(y==fa) continue;
		dfs(y,x,mid);
        f[2][x]=max(delt(f[1][x]+f[1][y]+w,mid),f[2][x]+f[0][y]),
        f[1][x]=max(f[0][x]+f[1][y]+w,f[1][x]+f[0][y]),
        f[0][x]=f[0][x]+f[0][y];
	}
	f[0][x]=max(f[0][x],max(delt(f[1][x],mid),f[2][x]));
}
int main()
{
	read(n),read(k);
	ll u,v,w;fo(1,i,n-1) read(u),read(v),read(w),g[u].pb({v,w}),g[v].pb({u,w});
	mem(f,-0x3f);k++;
	ll l=-1e12,r=1e12;
	while(l<=r)
	{
		ll mid=l+r>>1;mem(f,0);dfs(1,0,mid);
		if(f[0][1].y<=k) r=mid-1;
		else l=mid+1;
	}
	mem(f,0);dfs(1,0,l);
	wr(l*k+f[0][1].x),pr;
	return 0;
}

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号