DP练习

P10954 LCIS

思路

  • 令 \(f_{i,j}\) 表示遍历到了 \(a_i\),最后一个选 \(b_j\) 的方案数,那么可以得到转移方程 \(\begin{cases} f_{i,j}=f_{i,j-1}\space (a_i\ne b_j) \\f_{i,j}={max}^{k<j}_{k=1}f_{i-1,k}+1\space (a_i=b_j) \end{cases}\)
  • \({max}^{k<j}_{k=1}f_{i-1,k}\) 可以在循环 \(j\) 时一起算出来,复杂度降为 \(O(n^2)\)

ABCD

题面

有四个长度为 \(N\) 的数组 \(a,b,c,d\)。现在需要选择 \(N\) 个数构成数组 \(e\),满足 \(a_i\le e_i\le b_i\) 且 \(\sum^{N}_{i=1} e_i*c_i=0\),求所有方案中最大的 \(\sum^N_{i-1} e_i*d_i\)

解析

由于 \(a_i\) 可能为负,那么我们现将所有 \(a_i\) 拆出来,也就是要求 \(\sum_{i=1}^N e_i*c_i=\sum_{i=1}^N-a_i*c_i\),求最大的 \(\sum^N_{i-1} e_i*d_i\),其中 \(0\le e_i \le b_i-a_i\)

这就是多重背包,单调队列优化或二进制拆分优化都可

#include<bits/stdc++.h>
using namespace std;
const int N=205,M=100000,inf=1e9;
int a[N],b[N],c[N],d[N],f[N][(M<<1)+1],n,sum1,sum2;
deque<int> dq;
int main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=0;i<=n;i++)
		for(int j=0;j<=M<<1;j++) f[i][j]=-inf;
	f[0][0]=0;
	for(int i=1;i<=n;i++){
		cin>>a[i]>>b[i]>>c[i]>>d[i];
		sum1-=a[i]*c[i],sum2+=a[i]*d[i],b[i]-=a[i];
	}
	for(int i=1;i<=n;i++){
		for(int y=0;y<c[i];y++){
			dq.clear();
			for(int x=0;y+x*c[i]<=sum1;x++){
				while(!dq.empty()&&x-dq.front()>b[i]) dq.pop_front();
				while(!dq.empty()&&f[i-1][y+dq.back()*c[i]]-dq.back()*d[i]<=f[i-1][y+x*c[i]]-x*d[i]) dq.pop_back();
				dq.push_back(x);
				f[i][y+x*c[i]]=max(f[i][y+x*c[i]],f[i-1][y+dq.front()*c[i]]+(x-dq.front())*d[i]);
			}
		}
	}
	cout<<f[n][sum1]+sum2;
	return 0;
}

P2831 [NOIP 2016 提高组] 愤怒的小鸟

解析

  • 看到数据范围就知道是状压dp,设 \(f_{i,S}\) 为选到第 \(i\) 个,选取集合为 \(S\) 的答案
  • 可得转移方程为 \(\begin{cases} f[s|line[i][j]]=min(f[s]+1) \\f[s|1<<(i-1)]=min(f[s]+1) \end{cases}\),其中 \(line[i][j]\) 为经过 \(i,j\) 的抛物线经过点的集合此时复杂度为 \(O(Tn^22^n)\)
  • 考虑优化,发现每次转移只用考虑集合的最小点,因为
  1. 转移的顺序不影响答案
  2. 所有点一定会被考虑
  • 这样只用 \(f[s|line[lowbit(s)][j]]=f[s^(1<<lowbit(s))]+1\) 即可,复杂度 \(O(Tn2^n)\)

P7296 [USACO21JAN] Uddered but not Herd G

解析

  • 注意到两个测试点中有用字母都不大于20,可以状压dp
  • 考虑如何状压,我们按常理先令 \(f[i][S]\) 为已选 \(i\) 个,已放进字符串中的集合为 \(S\),那么每次转移,我们难以得到具体的字母表,但可以得到先后顺序,那么我们要考虑的就是两个字母的先后顺序的贡献
  • 回到原串上,假定字母表已知,贪心来看,当且仅当 \(s_i\) 在字母表中的位置在 \(s_{i+1}\) 的位置的后面时需要多听一次。因此对于一组字母先后 \((p,q)\),它的贡献就是所有 \(s_i=q,s_{i+1}=p\) 的个数
  • 设 \(v[i][j]\) 为所有 \(s_i=i,s_{i+1}=j\) 的个数,那么转移方程就是 \(f[i][S|1<<(i-1)]=f[i-1][S]+\sum_{j\in S}v[i][j]\)

CF1316E Team Building

解析

依旧状压,依旧先列方程,令 \(f_{i,S}\) 为遍历到第 \(i\) 个,选取集合为 \(S\),那么转移方程为 \(\begin{cases} f_{i,S|1<<j}=f_{i-1,s}+p_{i,j} \\f_{i,S}=f_{i-1,S}+a_i\space (cnt\le k) \end{cases}\),发现瓶颈在于无法确定每个状态的 \(cnt\),那我们不妨贪心的想,在不选进 \(S\) 的人中,一定是选 \(a_i\) 的最优,因此我们先按 \(a_i\) 从大到小排序,能不能加 \(a_i\) 只取决于是否满足 \(popcount(s)+k\le i\)(即去掉 \(S\) 中的后能否排到前 \(k\) 个)

CF1734F Zeros and Ones

数位dp

首先分析一下这个序列,发现 \(S_i=popcount(i) \space mod\space 2\),那么原问题相当于求 \(\sum^{m-1}_{i=0}popcount(i\oplus (i+n))\space mod \space 2\),也就是 \(i\) 和 \(i+n\) 二进制位不同的位数的奇偶性,考虑将 \(n\) 二进制拆分为 \(add\),从高到低遍历,令 \(f_{pos,i,prlen,st}\) 为当前遍历到第 \(pos\) 位,当前位为 \(i\),前面连续一个数的奇偶性为 \(prlen\),二进制位不同个数的奇偶性为 \(st\),那么则有转移 \(f_{pos,i\oplus add_{pos},i\oplus add_{pos}=0\space ?\space0\space :\space prlen\oplus 1,st\oplus add_{pos} \oplus (i \& add_{pos}=1\space?\space prlen\oplus 1\space :\space0) }=f_{pos-1,i,prlen,st}\)

其中 \(i\oplus add_{pos}=0\space ?\space0\space :\space prlen\oplus 1\):如果当前位为1,那么连续一个数加一,\(st\oplus add_{pos} \oplus (i \& add_{pos}=1\space?\space prlen\oplus 1\space :\space0)\):如果进位,那么连续一会变成只有一个一,相当于不同的多了 \(prelen+1\) 位

CF1528A Parsa's Humongous Tree

贪心,每次选左右端点一定比中间优,剩下就是树上dp板子

CF708C Centroids

如果已为重心,那就不用操作

如果不为重心,那么一次操作相当于从最大的子树中拆出一颗子树,要想操作后是重心,就要使拆出的子树和剩下的siz都小于等于n/2

那么对于每颗子树维护小于等于n/2的最大的siz,然后换根dp,由于换根时可能刚好是最大子树,因此要多维护一个次大值

锻造

题面

“欢迎啊,老朋友。”

一阵寒暄过后,厂长带他们参观了厂子四周,并给他们讲锻造的流程。

“我们这里的武器分成若干的等级,等级越高武器就越厉害,并且对每 一等级的武器都有两种属性值 \(b\) 和 \(c\),但是我们初始只能花 \(a\) 个金币来生产 1 把 0 级剑……”

“所以你们厂子怎么这么垃圾啊,不能一下子就造出来 999 级的武器 吗?”勇者不耐烦的打断了厂长的话。

“别着急,还没开始讲锻造呢……那我们举例你手中有一把 x 级武器和 一把 y 级武器 \((y = max(x − 1, 0))\) ,我们令锻造附加值 \(k = min(c_x, b_y)\),则 你有 \(k\over c_x\) 的概率将两把武器融合成一把 x + 1 级的武器。”

“……但是,锻造不是一帆风顺的,你同样有 \(1- {{k}\over c_x}\) 的概率将两把武器 融合成一把 \(max(x − 1, 0)\) 级的武器……”

勇者听完后暗暗思忖,他知道厂长一定又想借此机会坑骗他的零花钱, 于是求助这个村最聪明的智者——你,来告诉他,想要强化出一把 n 级的 武器,其期望花费为多少?

由于勇者不精通高精度小数,所以你只需要将答案对 998244353 取模即可。

解析

设 \(f_{i}\) 为锻造到 \(i\) 级的期望花费,那么显然有 \(f_i=(f_{i-1}+f_{i-1})*P_i\space ,P_i={k\over c_x}\),有因为有可能锻失败,那么就要补一把 \(i-1\) 级的剑,那么可得期望方程 \(f_i=(f_{i-1}+f_{i-2})*P_i\space + (f_i+f_{i-1})*(1-P_i)\),解方程得 \(f_i={f_{i-1}\over P_i}+f_{i-2}\),由于 \(n\) 较大,所以求逆元要用递归公式 \(inv(i)=-mod \space \% \space i\space*\space inv(mod \space \% \space i)\)

#include<bits/stdc++.h>
using namespace std;
const int N=4e5+5;
int mx[N],mx1[N],mx2[N],se[N],siz[N],ans[N],n;//mx:max size mx1:max size <= n/2 mx2:max size <=n/2 except root
vector<int> e[N];
void dfs1(int u,int fa){
	siz[u]=1;
	for(auto v:e[u]){
		if(v==fa) continue;
		dfs1(v,u);
		siz[u]+=siz[v];
		if(siz[mx[u]]<siz[v]) mx[u]=v;
		if(siz[mx1[u]]<siz[mx1[v]]) mx1[u]=mx1[v];
		if(siz[mx2[u]]<siz[mx1[v]]) se[u]=mx2[u],mx2[u]=mx1[v];
		else if(siz[se[u]]<siz[mx1[v]]) se[u]=mx1[v];
	}
	if(siz[u]<=n/2) mx1[u]=u;
}
void dfs2(int u,int fa){
	if(siz[mx[u]]<=n/2||siz[mx[u]]-siz[mx2[mx[u]]]<=n/2) ans[u]=1;
	for(auto v:e[u]){
		int m=mx[u],m1=mx1[u],m2=mx2[u],su=se[u];
		int vm=mx[v],vm1=mx1[v],vm2=mx2[v],sv=se[v];
		if(v==fa) continue;
		if(mx2[u]==mx1[v]) mx2[u]=se[u];
		siz[u]-=siz[v],siz[v]+=siz[u];
		if(siz[mx[v]]<siz[u]) mx[v]=u;
		if(siz[u]<=n/2) mx1[u]=u;
		else mx1[u]=mx2[u];
		if(siz[mx2[v]]<siz[mx1[u]]) mx2[v]=mx1[u];
		else if(siz[se[v]]<siz[mx1[u]]) se[v]=mx1[u];
		dfs2(v,u);
		mx[u]=m,mx1[u]=m1,mx2[u]=m2,se[u]=su;
		mx[v]=vm,mx1[v]=vm1,mx2[v]=vm2,se[v]=sv;
		siz[v]-=siz[u],siz[u]+=siz[v];
	}
}
int main(){
	cin>>n;
	for(int i=1;i<n;i++){
		int x,y;cin>>x>>y;
		e[x].push_back(y),e[y].push_back(x);
	}
	dfs1(1,0);
	dfs2(1,0);
	for(int i=1;i<=n;i++) cout<<ans[i]<<" ";
	return 0;
}
posted @ 2026-10-30 14:54  Rainesxiao  阅读(3)  评论(0)    收藏  举报