AT_dp

AT_dp_a Frog 1

设 \(dp_i\) 表示从 \(1\) 跳到 \(n\) 至少需要多少费用,那么 \(i\) 只能从 \(i-1\) 或 \(i-2\) 跳过来,因此得到

\[dp_i=\min\{dp_{i-1}+|a_i-a_{i-1}|,dp_{i-2}+|a_i-a_{i-2}|\} \]

时间复杂度 \(O(n)\)。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,a[100005],dp[100005];
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=2;i<=n;i++){
		dp[i]=dp[i-1]+abs(a[i]-a[i-1]);
		if(i>2)dp[i]=min(dp[i],dp[i-2]+abs(a[i]-a[i-2]));
	}
	cout<<dp[n];
	return 0;
}

AT_dp_b Frog 2

看见 \(K\) 非常小,所以可以暴力。

设 \(dp_i\) 表示从 \(1\) 跳到 \(n\) 至少需要多少费用,那么与上题类似

\[dp_i=\min_{j=1}^{min(i-1,k)}dp_{i-j}+|a_i-a_{i-j}| \]

时间复杂度 \(O(NK)\)。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,a[100005],dp[100005],k;
signed main(){
	cin>>n>>k;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=2;i<=n;i++){
		dp[i]=LONG_LONG_MAX;
		for(int j=1;j<=min(i-1,k);j++)dp[i]=min(dp[i],dp[i-j]+abs(a[i]-a[i-	j]));
	}
	cout<<dp[n];
	return 0;
}

AT_dp_c Vacation

还是很好想的。设 \(dp_{i,j}\) 表示第 \(i\) 天选第 \(j\) 种事情的最大幸福值,那么

\[dp_{i,0}=\max\{dp_{i-1,1},dp_{i-1,2}\}+a_i \]

\[dp_{i,1}=\max\{dp_{i-1,0},dp_{i-1,2}\}+b_i \]

\[dp_{i,2}=\max\{dp_{i-1,0},dp_{i-1,1}\}+c_i \]

最终答案是 \(\max\{dp_{n,0},dp_{n,1},dp_{n,2}\}\)。

时间复杂度 \(O(n)\)。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,a[100005],b[100005],c[100005],dp[100005][3];
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i]>>b[i]>>c[i];
	for(int i=1;i<=n;i++){
		dp[i][0]=max(dp[i-1][1],dp[i-1][2])+a[i];
		dp[i][1]=max(dp[i-1][0],dp[i-1][2])+b[i];
		dp[i][2]=max(dp[i-1][0],dp[i-1][1])+c[i];
	}
	cout<<max({dp[n][0],dp[n][1],dp[n][2]});
	return 0;
}

AT_dp_d Knapsack 1

01背包板子。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,W,dp[100005],v[105],w[105],ans;
signed main(){
	cin>>n>>W;
	for(int i=1;i<=n;i++)cin>>w[i]>>v[i];
	for(int i=1;i<=n;i++)for(int j=W;j>=w[i];j--)dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
	for(int i=0;i<=W;i++)ans=max(ans,dp[i]);
	cout<<ans;
	return 0;
}

AT_dp_e Knapsack 2

注意到 \(\sum{v_i}\le 10^5\),所以我们可以设 \(dp_i\) 表示选择的物品价值总和是 \(i\) 中的花费最小值,然后类似转移一下就行了,答案也很好求。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,W,w[105],v[105],dp[100005];
signed main(){
	cin>>n>>W;
	for(int i=1;i<=n;i++)cin>>w[i]>>v[i];
	memset(dp,0x3f,sizeof dp);
	dp[0]=0;
	for(int i=1;i<=n;i++)for(int j=100000;j>=v[i];j--)dp[j]=min(dp[j],dp[j-v[i]]+w[i]);
	for(int i=100000;i;i--)if(dp[i]<=W){
		cout<<i;
		return 0;
	}
}

AT_dp_f LCS

求两个字符串的最长公共子序列是很简单的,我们只需要想如何输出最长公共子序列就行了。

实际上可以找到最大的 \(dp_{i,j}\) 对应的 \(i\) 和 \(j\),接着逆着 dp 的方向进行倒退,就能求出最长公共子序列了。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int dp[3005][3005],ans;
string s,t,anss;
signed main(){
	cin>>s>>t;
	for(int i=0;i<s.size();i++)for(int j=0;j<t.size();j++){
		if(s[i]==t[j])dp[i+1][j+1]=dp[i][j]+1;
		else dp[i+1][j+1]=max(dp[i][j+1],dp[i+1][j]);
	}
	for(int i=1;i<=s.size();i++)for(int j=1;j<=t.size();j++)ans=max(ans,dp[i][j]);
	for(int i=1;i<=s.size();i++)for(int j=1;j<=t.size();j++)if(dp[i][j]==ans){
		int ii=i,jj=j;
		while(ii>0&&jj>0){
			if(s[ii-1]==t[jj-1])anss+=s[ii-1],ii--,jj--;
			else if(dp[ii][jj-1]<dp[ii-1][jj])ii--;
			else jj--;
		}
		reverse(anss.begin(),anss.end());
		cout<<anss;
		return 0;
	}
	return 0;
}

AT_dp_g Longest Path

直接 DAG 上 dp。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,u,v,dp[100005],vis[100005],ans;
vector<int> ve[100005];
void dfs(int u){
	if(vis[u])return;
	vis[u]=1;
	for(int v:ve[u]){
		dfs(v);
		dp[u]=max(dp[u],dp[v]+1);
	}
}
signed main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++)cin>>u>>v,ve[u].emplace_back(v);
	for(int i=1;i<=n;i++)if(!vis[i])dfs(i);
	for(int i=1;i<=n;i++)ans=max(ans,dp[i]);
	cout<<ans;
	return 0;
}

AT_dp_h Grid 1

可以直接 dp。

设 \(dp_{i,j}\) 表示走到 \((i,j)\) 的方案数,那么

\[dp_{i,j}=\begin{cases} dp_{i-1,j}+dp_{i,j-1} & a_{i,j}='.'\\ 0 & otherwise \end{cases}\\ \]

时间复杂度 \(O(HW)\)。

#include<bits/stdc++.h>
#define mod 1000000007
#define int long long
using namespace std;
int h,w,dp[1005][1005];
char a[1005][1005];
signed main(){
	cin>>h>>w;
	for(int i=1;i<=h;i++)cin>>(a[i]+1);
	dp[1][1]=1;
	for(int i=1;i<=h;i++)for(int j=1;j<=w;j++){
		if(a[i][j]=='#'||(i==1&&j==1))continue;
		dp[i][j]=dp[i-1][j]+dp[i][j-1],dp[i][j]%=mod;
	}
	cout<<dp[h][w];
	return 0;
}

AT_dp_i Coins

大力 dp。

设 \(dp_{i,j}\) 表示前 \(i\) 个数中有 \(j\) 个正面,那么很显然的

\[dp_{i,j}=dp_{i-1,j}\times(1-p_i)+dp_{i-1,j-1}\times{p_i} \]

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
double ans,p[3005],dp[3005][3005];
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>p[i];
	dp[1][0]=1-p[1],dp[1][1]=p[1];
	for(int i=2;i<=n;i++)for(int j=0;j<=i;j++){
		dp[i][j]=dp[i-1][j]*(1.0-p[i])+dp[i-1][j-1]*p[i];
	}
	for(int j=(n+1)/2;j<=n;j++)ans+=dp[n][j];
	printf("%.10lf",ans);
	return 0;
}

AT_dp_j Sushi

可以想到,答案与 \(a_i\) 的排列方式无关。

那么求出 \(1\) 的数量,\(2\) 的数量,\(3\) 的数量,然后用 \(dp(a,b,c)\) 表示有 \(a\) 个 \(1\),\(b\) 个 \(2\),\(c\) 个 \(3\) 的答案,那么容易得到\(\displaystyle{dp(a,b,c)=\frac{n}{a+b+c}+\frac{a}{a+b+c}\times dp(a-1,b,c)+\frac{b}{a+b+c}\times dp(a+1,b-1,c)+\frac{c}{a+b+c}\times dp(a,b+1,c-1)}\)

这四个式子分别是选了 \(0\) 到 \(3\) 的期望个数。

然后就能记忆化搜索了。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,x,cnt1,cnt2,cnt3;
double dp[305][305][305];
double dfs(int a,int b,int c){
	if(!a&&!b&&!c)return 0;
	if(dp[a][b][c])return dp[a][b][c];
	double ans=double(n)/double(a+b+c);
	if(a>0)ans+=dfs(a-1,b,c)*double(a)/double(a+b+c);
	if(b>0)ans+=dfs(a+1,b-1,c)*double(b)/double(a+b+c);
	if(c>0)ans+=dfs(a,b+1,c-1)*double(c)/double(a+b+c);
	return dp[a][b][c]=ans;
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>x;
		if(x==1)cnt1++;
		if(x==2)cnt2++;
		if(x==3)cnt3++;
	}
	printf("%.10lf",dfs(cnt1,cnt2,cnt3));
	return 0;
}

AT_dp_k Stones

博弈论。

可以类似背包的转移。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,dp[100005],a[105],k;
signed main(){
	cin>>n>>k;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=k;i++)for(int j=1;j<=n;j++)if(i>=a[j])if(!dp[i-a[j]])dp[i]=1;
	if(dp[k])cout<<"First";
	else cout<<"Second";
	return 0;
}

AT_dp_l Deque

可以区间 dp。

\(dp_{i,j}\) 表示剩下 \(i\) 到 \(j\) 时的答案。

然后如果剩下段的长度与 \(n\) 同奇偶,那么

\[dp_{i,j}=\max\{dp_{i+1,j}+a_i,dp_{i,j-1}+a_j\} \]

如果剩下段的长度与 \(n\) 不同奇偶,那么

\[dp_{i,j}=\min\{dp_{i+1,j}-a_i,dp_{i,j-1}-a_j\} \]

答案是 \(dp_{1,n}\)。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,dp[3005][3005],a[3005];
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int len=1;len<=n;len++){
		for(int i=1;i+len-1<=n;i++){
			int j=i+len-1;
			if((len&1)==(n&1))dp[i][j]=max(dp[i+1][j]+a[i],dp[i][j-1]+a[j]);
			else dp[i][j]=min(dp[i+1][j]-a[i],dp[i][j-1]-a[j]);
		}
	}
	cout<<dp[1][n];
	return 0;
}

AT_dp_m Candies

很显然的 dp 加上个前缀和优化就过了。

\(dp_{i,j}\) 表示前 \(i\) 个人分 \(j\) 个糖果,那么可以得到

\[dp_{i,j}=\sum_{k=max(0,j-a_i)}^{j}dp_{i-1,k} \]

然后直接前缀和优化就行了。

#include<bits/stdc++.h>
#define mod 1000000007
#define int long long
using namespace std;
int n,k,a[105],dp[105][100005],sum[105][100005];
signed main(){
	cin>>n>>k;
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=0;i<=k;i++)sum[0][i]=1;
	for(int i=1;i<=n;i++)dp[i][0]=sum[i][0]=1;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=k;j++){
			if(j>=a[i])dp[i][j]=((sum[i-1][j]-sum[i-1][j-a[i]-1])%mod+mod)%mod;
			else dp[i][j]=sum[i-1][j];
			sum[i][j]=(sum[i][j-1]+dp[i][j])%mod;
		}
	}
	cout<<dp[n][k];
	return 0;
}

AT_dp_n Slimes

区间 dp 板子。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,a[405],dp[405][405],sum[405];
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i],sum[i]=sum[i-1]+a[i];
	for(int len=2;len<=n;len++){
		for(int i=1;i+len-1<=n;i++){
			int j=i+len-1;
			dp[i][j]=LONG_LONG_MAX;
			for(int k=i;k<j;k++)dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+(sum[k]-sum[i-1])+(sum[j]-sum[k]));
		}
	}
	cout<<dp[1][n];
	return 0;
}

AT_dp_o Matching

有趣的状压。

\(dp_S\) 表示前一个集合的前 \(|S|\) 个数对应后一个集合的中属于 \(S\) 的个数,那么

\[dp_S=\sum_{i\in S,\{|S|,i\}\in E}dp_{S\backslash i} \]

时间复杂度 \(O(2^n\times n)\)

#include<bits/stdc++.h>
#define mod 1000000007
#define int long long
using namespace std;
int n,a[25][25],dp[4000005];
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)cin>>a[i][j];
	dp[0]=1;
	for(int S=1;S<(1<<n);S++){
		for(int i=1;i<=n;i++)if(S&(1<<(i-1)))if(a[__builtin_popcount(S)][i])dp[S]+=dp[S-(1<<(i-1))],dp[S]%=mod;
	}
	cout<<dp[(1<<n)-1];
	return 0;
}

AT_dp_p Independent Set

简单的树形 dp。

#include<bits/stdc++.h>
#define mod 1000000007
#define int long long
using namespace std;
int n,u,v,dp[100005][2];
vector<int> ve[100005];
void dfs(int u,int fa){
	dp[u][0]=dp[u][1]=1;
	for(int v:ve[u]){
		if(v==fa)continue;
		dfs(v,u);
		dp[u][0]*=(dp[v][0]+dp[v][1])%mod;
		dp[u][1]*=dp[v][0];
		dp[u][0]%=mod,dp[u][1]%=mod;
	}
}
signed main(){
	cin>>n;
	for(int i=1;i<n;i++)cin>>u>>v,ve[u].emplace_back(v),ve[v].emplace_back(u);
	dfs(1,0);
	cout<<(dp[1][0]+dp[1][1])%mod;
	return 0;
}

AT_dp_q Flowers

板子题,暴力套一个权值线段树优化就过了。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,h[200005],a[200005],dp[200005],ans;
struct tree{
	int l,r,maxx;
}tr[800005];
void pushup(int u){
	tr[u].maxx=max(tr[u<<1].maxx,tr[u<<1|1].maxx);
}
void build(int u,int l,int r){
	tr[u]={l,r};
	if(l==r)return;
	int mid=l+r>>1;
	build(u<<1,l,mid),build(u<<1|1,mid+1,r);
}
void change(int u,int pos,int k){
	if(tr[u].l==tr[u].r){
		tr[u].maxx=k;
		return;
	}
	int mid=tr[u].l+tr[u].r>>1;
	if(pos<=mid)change(u<<1,pos,k);
	else change(u<<1|1,pos,k);
	pushup(u);
}
int query(int u,int l,int r){
	if(tr[u].l>=l&&tr[u].r<=r)return tr[u].maxx;
	int mid=tr[u].l+tr[u].r>>1,res=LONG_LONG_MIN;
	if(l<=mid)res=max(res,query(u<<1,l,r));
	if(r>mid)res=max(res,query(u<<1|1,l,r));
	return res;
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>h[i];
	for(int i=1;i<=n;i++)cin>>a[i];
	build(1,1,2e5);
	for(int i=1;i<=n;i++){
		dp[i]=query(1,1,h[i])+a[i];
		change(1,h[i],dp[i]);
	}
	for(int i=1;i<=n;i++)ans=max(ans,dp[i]);
	cout<<ans;
	return 0;
}

AT_dp_r Walk

一个经典的结论是,如果要求一个图有多少个长度为 \(k\) 的路径,那么可以把邻接矩阵做 \(k\) 次幂,然后这个邻接矩阵中所有数的和就是答案了。

那么这题可以直接这样做,时间复杂度 \(O(n^3\log k)\)。

#include<bits/stdc++.h>
#define int long long 
#define mod 1000000007
using namespace std;
int n,k,anss;
struct matrix{
	int a[55][55],len;
	friend matrix operator*(const matrix&a,const matrix&b){
		matrix c;c.len=a.len;
		for(int i=1;i<=a.len;i++)for(int j=1;j<=a.len;j++){
			c.a[i][j]=0;
			for(int k=1;k<=a.len;k++)c.a[i][j]+=a.a[i][k]*b.a[k][j]%mod,c.a[i][j]%=mod;
		}
		return c;
	}
}mt,ans;
matrix ksm(matrix a,int b){
	matrix ans=a;b--;
	while(b){
		if(b&1)ans=ans*a;
		a=a*a;
		b>>=1;
	}
	return ans;
}
signed main(){
	cin>>n>>k,mt.len=n;
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)cin>>mt.a[i][j];
	ans=ksm(mt,k);
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)anss+=ans.a[i][j],anss%=mod;
	cout<<anss;
	return 0;
}

AT_dp_s Digit Sum

数位 dp 入门题。

#include<bits/stdc++.h>
#define mod 1000000007
#define int long long
using namespace std;
int d,a[10005],dp[10005][105];
string s;
int dfs(int now,int md,int tp){
	if(!now){
		if(!md)return 1;
		else return 0;
	}
	if(!tp&&dp[now][md]>=0)return dp[now][md];
	int maxx=tp?a[now]:9,res=0;
	for(int i=0;i<=maxx;i++)res+=dfs(now-1,(md+i)%d,tp&&i==maxx),res%=mod;
	if(!tp)dp[now][md]=res;
	return res;
}
signed main(){
	cin>>s>>d;
	memset(dp,-0x3f,sizeof dp);
	for(int i=0;i<s.size();i++)a[s.size()-i]=s[i]-'0';
	cout<<(dfs(s.size(),0,1)-1+mod)%mod;
	return 0;
}

AT_dp_t Permutation

\(dp_{i,j}\) 表示前 \(i\) 个数中的最后一个数是 \(j\),而且这 \(i\) 个数是 \(1\) 到 \(i\) 的方案数。

那么容易想到如果 \(s_{i-1}\) 为大于号时

\[dp_{i,j}=\sum_{k=j}^{i-1}dp_{i-1,k} \]

如果 \(s_{i-1}\) 为小于号时

\[dp_{i,j}=\sum_{k=1}^{j-1}dp_{i-1,k} \]

加个前缀和优化就能过,复杂度 \(O(n^2)\)。

#include<bits/stdc++.h>
#define mod 1000000007
#define int long long
using namespace std;
int n,f[3005][3005],sum[3005][3005],ans;
string s;
signed main(){
	cin>>n>>s;s=" "+s;
	f[1][1]=sum[1][1]=1;
	for(int i=2;i<=n;i++)for(int j=1;j<=i;j++){
		if(s[i-1]=='>'){
			f[i][j]=sum[i-1][i-1]-sum[i-1][j-1];
			f[i][j]+=mod,f[i][j]%=mod;
		}
		else{
			f[i][j]=sum[i-1][j-1];
			f[i][j]%=mod;
		}
		sum[i][j]=sum[i][j-1]+f[i][j],sum[i][j]%=mod;
	}
	for(int i=1;i<=n;i++)ans+=f[n][i],ans%=mod;
	cout<<ans;
	return 0;
}

AT_dp_u Grouping

很好想的状压。

先预处理出每个集合 \(S\) 如果分成一组的贡献 \(val_S\),然后设 \(dp_S\) 表示集合 \(S\) 分组可以得到的最大值,那么

\[dp_S=\max_{V\subseteq S}dp_{S\backslash V}+val_V \]

时间复杂度 \(O(3^n)\)。

#include<bits/stdc++.h>
#define mod 1000000007
#define int long long
using namespace std;
int n,dp[1000005],a[25][25],c[1000005];
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)cin>>a[i][j];
	for(int S=1;S<(1<<n);S++)for(int j=1;j<=n;j++)if(S&(1<<(j-1)))for(int k=j+1;k<=n;k++)if(S&(1<<(k-1)))c[S]+=a[j][k];
	for(int S=1;S<(1<<n);S++)for(int V=S;V;V=(V-1)&S)dp[S]=max(dp[S],dp[S-V]+c[V]);
	cout<<dp[(1<<n)-1];
	return 0;
}

AT_dp_v Subtree

待补。

AT_dp_w Intervals

今年NOIP T4的原题。待补。

AT_dp_x Tower

我们可以贪心地想到如果两个箱子 \(i\) 和 \(j\),且 \(s_i-w_j<s_j-w_i\) 的话,\(i\) 一定比 \(j\) 优先选择。

把这个式子变一下变成 \(s_i+w_i<s_j+w_j\),就能排序了。

然后使用类似 01 背包的方法就做完了。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,dp[100005],ans; 
struct nd{
	int w,s,v;
	friend bool operator<(const nd&a,const nd&b){
		return a.w+a.s<b.w+b.s;
	}
}a[1005];
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>a[i].w>>a[i].s>>a[i].v;
	sort(a+1,a+n+1);
	memset(dp,-0x3f,sizeof dp),dp[0]=0;
	for(int i=1;i<=n;i++)for(int j=a[i].s;~j;j--)dp[j+a[i].w]=max(dp[j+a[i].w],dp[j]+a[i].v);
	for(int i=0;i<=20000;i++)ans=max(ans,dp[i]);
	cout<<ans;
	return 0;
}

AT_dp_y Grid 2

待补。

AT_dp_z Frog 3

待补。

posted @ 2023-11-30 19:35  DerrickLo  阅读(224)  评论(1)    收藏  举报