其他分享
首页 > 其他分享> > 小清新简单dp题(二)

小清新简单dp题(二)

作者:互联网

9. P1453 城市环路

很容易发现这其实就是一道基环树上的 dp 题。

如果是在普通树上该怎么做呢?考虑设 \(dp_{i,0/1}\) 表示以 \(i\) 为根的子树内,\(i\) 节点不选/选时贡献的最大值,有转移方程:

\[dp_{i,0}=\sum_{j\in son_i} \max(dp_{j,0},dp_{j,1}) \]

\[dp_{i,1}=\sum_{j\in son_i} dp_{j,0} \]

基环树相比普通树其实就只是多了一条边而已,而一条边的意义就在于相比于普通树,该边两端的节点也不能被同时选择。

于是我们可以利用并查集,找出第一对在加边过程中在并查集同一集合中的节点 \(S,T\),可以确定它们就是基环上的两个相邻节点。

然后我们可以以 \(S\) 和 \(T\) 分别作为根节点做两边 dp,将 \(dp_{S,0}\) 和 \(dp_{T,0}\) 取最大值即为最终答案。由于两个值无论哪个都表示不选其中的一个节点,所以可以证明在最终的方案中 \(S\) 和 \(T\) 一定不会同时被选,即可完成此题。


10. P3594 [POI2015]WIL-Wilcze doły

双指针+单调队列。

贪心地想,由于序列中的数都是正整数,我们在选中区间覆盖 \(d\) 个长度一定比覆盖 \(d-1\) 个长度优。即使我们只选了 \(d-1\) 个数,多选一个一定还是更优的(长度变长了)。所以我们可以将覆盖长度默认为 \(d\)。而默认之后,可以供我们选择覆盖、长度为 \(d\) 的区间只剩下了 \(n-d+1\) 个,且从左到右均匀分布。

题意让我们先覆盖一段为 \(0\) 在选区间,这样太麻烦,我们不要。

于是我们反过来想。如果我们先选区间,再选择其中的一段覆盖,那么由于覆盖长度确定,我们覆盖的一定是该区间之中区间和最大的一段长度为 \(d\) 的区间。所以我们可以考虑先选择区间。

考虑找到每一个 \(r\),随着 \(r\) 的值不断递增,\(l\) 一定也是单调递增的。又因为我们选择的覆盖区间一定是所选区间的一部分,所以覆盖区间大体上也是单调递增的。

于是我们突然想到了单调队列。

考虑从 \(d\) 开始枚举每一个 \(r\),并将该 \(r\) 产生的新的长度为 \(d\) 的区间 \((r-d+1,r)\) 按照正常程序放入单调队列(剔除队尾区间和小于该区间区间和的区间(莫名绕口))。注意放入单调队列时,由于我们可以通过 \(r\) 来推出区间和,而不能通过区间和推出 \(r\),所以只需要放入 \(r\) 即可代表整个区间。

然后我们再来审视目前的区间,如果此时的区间和减去最大的覆盖区间和仍然大于 \(p\)(覆盖区间和即为单调队列队首区间的区间和),我们就可以将 \(l\) 加 \(1\),并剔除单调队列队首在所选区间范围之外的覆盖区间。

每次枚举 \(r\) 之后将 \(ans\) 和目前区间的答案取最大值即可。

for (int l=1,r=d;r<=n;r++){
	while (head<=tail&&len(deq[tail])<len(r)) --tail;
	deq[++tail]=r;
	while (sum[r]-sum[l-1]-len(deq[head])>p){
		++l;
		if (head<=tail&&deq[head]<l+d-1) ++head;
	}
	ans=max(ans,r-l+1);
}

11. P3572 [POI2014]PTA-Little Bird

首先很容易想到,设 \(dp_i\) 为到第 \(i\) 个树上时的最小代价,则状态转移方程为:

\[dp_{i}=\min(dp_j+[d_j\le d_i]\ | \ i-k\le j<i) \]

但是由于数据范围要求我们在 \(O(n)\) 的时间内做完本题,而如果通过枚举前面的 \(dp\) 数组转移当前 \(dp\) 项的复杂度却是 \(O(n^2)\) 的,必定超时,所以我们不得不思考优化方法。

考虑如果现在有两个合法决策 \(x\) 和 \(y\) 可以供 \(dp_i\) 选择,其中 \(x<y\),那么贪心地来看,如果满足下面两个条件之一:

那么决策 \(x\) 就可以抛弃掉了。否则,\(x\) 也不会比 \(y\) 更劣。

看到这个想到了什么?对于一个决策,既可以由于离开可供选择的范围而被剔除,也可以被后来的更优决策剔除……

单调队列!!!!

我们可以对于所有决策动态建立一个单调队列!

考虑循环枚举每一个 \(dp_i\)。首先由于开始就在第一个树上,所以 \(dp_1=0\),然后将 \(1\) 塞入单调队列。对于 \(dp_2\) 到 \(dp_n\),我们只需要每次选择单调队列队首决策即为当前的最优决策,直接转移即可。然后根据决策筛选条件(上面两个)按照正常程序将当前 \(dp\) 项放入队列,最后 \(dp_n\) 即为答案。

dq[++tail]=1;
for (int i=2;i<=n;i++){
	dp[i]=dp[dq[head]];
	if (d[dq[head]]<=d[i]) dp[i]++;
	while (head<=tail&&(dp[dq[tail]]>dp[i]||dp[dq[tail]]==dp[i]&&d[dq[tail]]<d[i])) --tail;
	dq[++tail]=i;
	if (head<=tail&&dq[head]<=i-k) ++head;
}
printf("%d\n",dp[n]);

12. P3287 [SCOI2014]方伯伯的玉米田

首先要证明一个性质:每次拔高区间的右端点是 \(n\) 一定不劣。

怎么证明呢?我们不妨感性地思考一下。假设我们选择了区间 \([x,n-1]\),那么

综上所述,我们可以默认每次拔高区间的右端点是 \(n\) 。

那么我们可以设 \(dp_{i,j}\) 表示以 \(i\) 结尾且拔高过 \(j\) 次时的最长不下降子序列的长度。则状态转移方程显然为:

\[dp_{i,j}=\max(dp_{x,y})+1\ |\ x\le i,y\le j,h_x+y\le h_i+j \]

方程我们推出来了,可以如果使用朴素算法四层枚举,复杂度将会达到恐怖的 \(O(n^2k^2)\),显然舍去。

所以我们考虑使用数据结构动态维护 \(dp_{x,y}\) 的最大值,选用二维线段树即可。由于没有修改操作,我们这里选用了常数较小的二维树状数组。

考虑两层循环分别正序枚举 \(i\) 和倒序枚举 \(j\)(类似背包问题原理),并在每次枚举后先查询前面 \(dp_{x,y}\) 的最大值,用它更新答案后,再将最大值放入树状数组,持续维护即可。

最终输出的答案即为最终答案。

int lowbit(int x){ return x&(-x); }
int query(int nh,int nk){
	int ans=0;
	for (int i=nh;i;i-=lowbit(i))
		for (int j=nk;j;j-=lowbit(j))
			ans=max(ans,c[i][j]);
	return ans;
}
void updata(int nh,int nk,int val){
	for (int i=nh;i<=ma+k;i+=lowbit(i))
		for (int j=nk;j<=k+1;j+=lowbit(j))
			c[i][j]=max(c[i][j],val);
}
int main(){
	n=read(),k=read();
	for (int i=1;i<=n;i++) a[i]=read(),ma=max(ma,a[i]);
	for (int i=1;i<=n;i++)
		for (int j=k;j>=0;j--){
			int x=query(a[i]+j,j+1)+1;
			ans=max(ans,x);
			updata(a[i]+j,j+1,x);
		}
	printf("%d\n",ans);
	return 0;
}

13. P2900 [USACO08MAR]Land Acquisition G

首先如果我们将每一个土地看成一块矩形,那么对于一个土地,如果存在另一个土地长宽都大于等于它,那么前一个土地对答案一定是没有任何贡献的,因为我们可以在购买后一个土地的时候顺便就将前一个土地买了。所以我们可以在输入之后就将所有矩形排序,最终使得所有土地的长度单调递增,宽度单调递减,将没有用的土地剔除出去、

考虑朴素做法,对于 \(dp_i\),朴素做法的状态转移方程显然为:

\[dp_{i}=\min(dp_i,dp_j+w_{j+1}h_i) \]

枚举 \(j\) 则复杂度达到 \(n^2\),显然不行,那么我们考虑决策。

对于两个决策 \(j\) 和 \(k\),如果 \(j\) 优于\(k\),那么则

\[dp_j+w_{j+1}h_i\le dp_k+w_{k+1}h_i \]

移项得

\[dp_j-dp_k\le(w_{k+1}-w_{j+1})h_i \]

所以

\[h_i\ge\frac{dp_j-dp_k}{w_{k+1}-w_{j+1}} \]

于是我们用单调队列的方式维护最优决策即可。

单调队列维护时考虑两点:

  1. 维护队首。由于上面的 \(h_i\) 是单调递增的,所以即使刚开始的时候 \(h_i\) 小于右侧式子,但一旦当 \(h_i\) 大于等于右侧式子时,便永远大于了。

由此我们可以定义一个名词——临界点——当过了这个点之后,设 \(j<k\),决策 \(j\) 的贡献就永远劣于 \(k\) 了。此时我们便可以将 \(j\) 删除。

表现在单调队列上就是,当队首决策劣于队列第二个决策时,我们便可以删除队首决策,因为它将永远没有用了。(可以看做越往后的决策越偏发展型)

而表现在这道题上则是,如果队首决策和队列第二个决策满足上述式子,则可以将队首决策删去。

维护队首后便可以去队首最优决策与 \(dp_i\) 进行 \(dp\)。

  1. 维护队尾。考虑三个决策 \(i\),\(j\),\(k\)。

如果 \(i\) 和 \(j\) 的临界点大于 \(j\) 和 \(k\) 的临界点,那么当 \(j\) 优于 \(i\)、将 \(i\) 剔除时,\(k\) 早已经优于 \(j\) 了。所以我们便可以在对 \(dp_i\) 转移过后再维护一下队尾,将没用的 \(j\) 剔除后,将当前决策 \(k\)(即 \(i\))放入队尾即可。

最后在 \(dp_n\) 转移过后输出答案即可。

double slope(int j,int k){
	return 1.0*(dp[j]-dp[k])/(ear[k+1].x-ear[j+1].x);
}

dq[++tail]=0;
for (int i=1;i<=n;i++){
	while (head<tail&&slope(dq[head],dq[head+1])<=ear[i].y) ++head;
	dp[i]=dp[dq[head]]+ear[dq[head]+1].x*ear[i].y;
	while (head<tail&&slope(dq[tail-1],dq[tail])>=slope(dq[tail],i)) --tail;
	dq[++tail]=i;
}

14. P1654 OSU!

考虑目前该进行第 \(i\) 次操作,前面已经连击了 \(x\) 次,那么若此次成功,则分数贡献为 \((x+1)^3-x^3=3x^2+3x+1\).

故设 \(E(f)_ i\) 为以 \(i\) 为结尾的总得分,则其显然为以 \(i-1\) 结尾的得分和第 \(i\) 次操作可能的得分之和。

设 \(E(x)_ i\) 为以 \(i\) 为结尾的连击长度 \(x\) 的期望 ,则

\[E(x)_ i=p_i\times(E(x)_ {i-1}+1)+(1-p_i)\times0=p_i(E(x)_ {i-1}+1) \]

升一维,考虑 \(x^2\) 的期望大小,则同理:

\[E(x^2)_ i=p_ i(E(x^2)_ {i-1}+2E(x)_ {i-1}+1) \]

综上可推出第 \(i\) 次操作可能的得分,得出转移方程:

\[E(f)_ i=E(f)_ {i-1}+p_i(3E(x^2)_{i-1}+3E(x)_{i-1}+1) \]

for (int i=1;i<=n;i++){
	scanf("%lf",&p);
	p1[i]=(p1[i-1]+1)*p;
	p2[i]=(p2[i-1]+2*p1[i-1]+1)*p;
	ans[i]=ans[i-1]+(3*p2[i-1]+3*p1[i-1]+1)*p;
}

15. P2135 方块消除

首先很容易想到用区间 dp,用 \(dp_{l,r}\) 表示区间 \([l,r]\) 消掉后分数的最大值。但很显然,这个设置并不能概括掉所有的状态,我们无法用这个状态进行有效转移。

为什么不能转移呢?仔细思索,我们发现这个设置无法表示例如 10101 这类方块。正解应该是先依次消去两个 0,再连续消去三个 1,但是显然我们无法用 \(dp_{l,r}\) 表示这个“连续消去”。

再深入思考,我们有时并不想一次性的消掉某个区间,而是想让这个区间的右端向左放,延迟消去。而我们要设的状态需要能够表示这个“延迟”的行为。

于是考虑设 \(dp_{l,r,p}\) 表示消去区间 \([l,r]\),且 \(r\) 右侧连着 \(p\) 个跟 \(r\) 同色的方块,消去之后贡献的最大值。

好奇怪的状态/fad

那么怎么转移呢?首先肯定需要采用区间 dp 的套路,枚举 \(k\),将 \([l,r]\) 拆成 \([l,k]\) 和 \([k+1,r]\) 分别转移。

毫无疑问,\([l,r]\) 的贡献可以是两个子区间的贡献之和;而如果 \(r\) 与 \(k\) 的颜色相同,则可以转化为区间 \([k+1,r-1]\) 的贡献,以及区间 \([l,k]\) 且右侧接上若干同色方块的贡献之和。

具体来讲,转移方程为:

\[dp_{i,j,p}=\max(dp_{i,k,0}+dp_{k+1,r,p},dp_{i,k,p+g_r}+dp_{k+1,r-1,0}),i\le k<r \]

而由于该转移方程要用到的状态比较稀疏,许多状态都不会被用到,所以可以考虑采用记忆化搜索的方式进行转移,可以优化掉非常大的常数。

int solve(int l,int r,int ex){
	if (l>r) return 0;
	if (dp[l][r][ex]) return dp[l][r][ex];
	if (l==r) return dp[l][r][ex]=(g[l]+ex)*(g[l]+ex);
	int ans=0;
	for (int k=l;k<r;k++){
		ans=max(ans,solve(l,k,0)+solve(k+1,r,ex));
		if (col[k]==col[r]) ans=max(ans,solve(k+1,r-1,0)+solve(l,k,ex+g[r]));
	}
	return dp[l][r][ex]=ans;
}

16. P2507 [SCOI2008]配对

有一个很明显的贪心是,尽量配对与自己排名相近的整数。

例如如果去掉不允许相同整数配对的条件,则将两个数组都升序或降序排序后,相对应的项配对,答案一定最优。

那么加上这个条件怎么做呢?

由于这个条件的干扰,相对应的项并不一定可以配对。但索性规定了两个数组内部元素互不相同,故对于一个整数,最多只有一个整数与其相同。

那么如果让每个整数与和自己排名相差±1的整数配对是否可行呢?显然不行。考虑对于下面数组 \(A\{1,2,3\},B\{1,2,3\}\),显然无法通过±1的干扰进行配对。

那么再退一步贪心,±2的排名是否可行呢?可以证明,是可以的。

那么如果我们将两个数组排序后,将每个整数与和其排名相差不超过 \(2\) 的另一个数组中的整数连边,是完全可以跑费用流的。不过很慢就是了

考虑 dp,设 \(dp_{i}\) 表示两个数组前 \(i\) 个整数互相匹配后的最小差值之和,对于前两项则有如下转移方程:

\[dp_{1}=|a_1-b_1| \]

\[dp_2=\min(|a_1-b_1|+|a_2-b_2|,|a_1-b_2|+|a_2-b_1|) \]

从第三项开始,第 \(i\) 项可以由前三项交换匹配,也可以前两项交换匹配,或直接匹配第 \(i\) 项。注意转移时若两项绝对值为 \(0\) ,则直接赋为极大值。

dp[1]=calc(a[1],b[1]);
dp[2]=min(calc(a[1],b[2])+calc(a[2],b[1]),calc(a[1],b[1])+calc(a[2],b[2]));
for (int i=3;i<=n;i++){
	dp[i]=min(min(dp[i-2]+calc(a[i-1],b[i])+calc(b[i-1],a[i]),dp[i-1]+calc(a[i],b[i])),
			min(dp[i-3]+calc(a[i-2],b[i])+calc(a[i-1],b[i-1])+calc(a[i],b[i-2]),
			min(dp[i-3]+calc(a[i-2],b[i-1])+calc(a[i-1],b[i])+calc(a[i],b[i-2]),
				dp[i-3]+calc(a[i-2],b[i])+calc(a[i-1],b[i-2])+calc(a[i],b[i-1]))));
}
printf("%lld\n",dp[n]);

标签:int,我们,队列,dp,简单,区间,清新,单调
来源: https://www.cnblogs.com/ydtz/p/16484428.html