AT_dp 做题笔记

持续更新。

更好的阅读体验?

更朴素的阅读体验?

未完成题目

AT_dp_v, AT_dp_w, AT_dp_y, AT_dp_z。

AT_dp_a

Solution

青蛙只能从 \(i-1\)\(i-2\) 跳过来,所以转移方程自然地就是 \(dp_i=min(dp_{i-1}+|h_i-h_{i-1}|,dp_{i-2}+|h_i-h_{i-2}|)\)

Code

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

AT_dp_b

Solution

基本同上,不过转移方程涉及 \(k\) 个其余状态,稍加修改即可。

Code

#include <bits/stdc++.h>
using namespace std;
long long h[int(1e5+10)],dp[int(1e5+10)];
int main()
{
	memset(dp,0x3f,sizeof dp);
	int n,k;
	cin>>n>>k;
	dp[1]=0;
	for(int i=1;i<=n;i++) cin>>h[i];
	for(int i=2;i<=n;i++)
	{
		for(int j=1;j<=k;j++)
		{
			if(i<=j) break;
			dp[i]=min(dp[i],dp[i-j]+abs(h[i]-h[i-j]));
		}
	}
	cout<<dp[n];
	return 0;
}

AT_dp_c

Solution

\(dp_{i,j}\) 表示第 \(i\) 天做第 \(j\) 件事的最大幸福度,从上一天的另外两件事转移过来。

Code

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

AT_dp_d

Solution

01 背包板子,不必多讲。

Code

//2年前写的,码风巨丑
#include<bits/stdc++.h>
using namespace std;
long long M,N,w[205],c[205],f[100005];
int main(){
    cin>>N>>M;
    for(int i=1;i<=N;i++)cin>>w[i]>>c[i];
    for(int i=1;i<=N;i++){
        for(int j=M;j>=w[i];j--)f[j]=max(f[j],f[j-w[i]]+c[i]);
    }
    cout<<f[M];
    return 0;
}

AT_dp_e

Solution

与上一题不同,因为 \(W\) 巨大但是 \(w_i\) 很小,故考虑用价值作为状态,即 \(dp_i\) 表示达到价值 \(i\) 所需最小重量,转移方程就随便推也能推出来了。

Code

#include <bits/stdc++.h>
using namespace std;
long long dp[int(1e6+10)],w[110],v[110];
int main()
{
	long long n,m,sum=0;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>w[i]>>v[i];
		sum+=v[i];
	}
	memset(dp,0x3f,sizeof dp);
	dp[0]=0;
	for(int i=1;i<=n;i++)
	for(int j=sum;j>=v[i];j--)
	dp[j]=min(dp[j],dp[j-v[i]]+w[i]);
	for(int i=sum;i;i--) if(dp[i]<=m)
	{
		cout<<i;
		return 0;
	}
}

AT_dp_f

Solution

首先这是模板,但是要输出 LCS,那就标记每一次转移从哪一个状态转过来,输出的时候倒序输出 \(dp_{1,n}\) 的上一个状态的上一个状态的上一个状态……

Code

#include <bits/stdc++.h>
using namespace std;
string s,t;
int dp[3010][3010],type[3010][3010];
int main()
{
	cin>>s>>t;
	int n=s.size(),m=t.size();
	s=' '+s;
	t=' '+t;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	{
		if(s[i]==t[j])
		{
			dp[i][j]=dp[i-1][j-1]+1;
			type[i][j]=1;
		}
		else
		{
			if(dp[i][j-1]>dp[i-1][j])
			{
				dp[i][j]=dp[i][j-1];
				type[i][j]=2;
			}
			else
			{
				dp[i][j]=dp[i-1][j];
				type[i][j]=3;
			}
		}
	}
	string ans;
	for(int i=n,j=m;i>0&&j>0;)
	{
		if(type[i][j]==1)
		{
			ans=s[i]+ans;
			i--;
			j--;
		}
		else if(type[i][j]==2) j--;
		else i--;
	}
	cout<<ans;
	return 0;
}

AT_dp_g

Solution

DAG 最长路板。

Code

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
vector<int> G[N];
int dp[N];
int dfs(int u)
{
	if(dp[u]) return dp[u];
	for(auto v:G[u]) dp[u]=max(dp[u],dfs(v)+1);
	return dp[u];
}
int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int u,v;
		cin>>u>>v;
		G[u].push_back(v);
	}
	int ans=-1;
	for(int i=1;i<=n;i++) ans=max(ans,dfs(i));
	cout<<ans;
	return 0;
}

AT_dp_h

Solution

过河卒。

Code

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1e9+7;
string s[1010];
int dp[1010][1010];
signed main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>s[i],s[i]=' '+s[i];
	dp[1][1]=1;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	{
		if(s[i][j]=='#'||(i==1&&j==1)) continue;
		dp[i][j]=dp[i-1][j]+dp[i][j-1];
		dp[i][j]%=mod;
	}
	cout<<dp[n][m];
	return 0;
}

AT_dp_i

Solution

看一眼数据范围:\(N\) 特别小,想到二维状态,于是想到 \(dp_{i,j}\) 表示前 \(i\) 个硬币有 \(j\) 个向上的概率,\(dp_{i,j}\)\(dp_{i-1,j-1}\)\(dp_{i-1,j}\) 转移过来,剩下的很简单了。

Code

#include <bits/stdc++.h>
using namespace std;
const int N=3010;
double dp[N][N],p[N];
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>p[i];
	dp[0][0]=1;
	for(int i=1;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];
	}
	double ans=0;
	for(int i=(n+1)/2;i<=n;i++) ans+=dp[n][i];
	printf("%.10lf",ans);
	return 0;
}

AT_dp_j

Solution

先考虑最朴素的 dp,\(dp_{a,b,c,d}\) 表示还剩下 \(a\)\(0\) 寿司盘子,\(b\)\(1\) 寿司盘子,以此类推。转移方程:\(dp_{a,b,c,d}=\frac{n}{b+c+d}+\frac{b}{b+c+d}\times dp_{a+1,b-1,c,d}+\frac{c}{b+c+d}\times dp_{a,b+1,c-1,d}+\frac{d}{b+c+d}\times dp_{a,b,c+1,d-1}\)

然后发现始终有 \(a+b+c+d=n\),考虑压缩掉第一维,变成三维 dp,复杂度 \(O(n^3)\),可以通过,具体转移方程见代码。

Code

#include <bits/stdc++.h>
using namespace std;
double dp[310][310][310];int cnt[4];
int main()
{
	double n;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		int x;
		cin>>x;
		cnt[x]++;
	}
	for(int k=0;k<=n;k++)
	for(int j=0;j<=n;j++)
	for(int i=0;i<=n;i++)
	{
		double x=i+j+k;
		if(!i&&!j&&!k) continue;
		if(i) dp[i][j][k]+=dp[i-1][j][k]*(i/(x));
		if(j) dp[i][j][k]+=dp[i+1][j-1][k]*(j/(x));
		if(k) dp[i][j][k]+=dp[i][j+1][k-1]*(k/(x));
		dp[i][j][k]+=n/(x);
	}
	printf("%.10lf",dp[cnt[1]][cnt[2]][cnt[3]]);
	return 0;
}

AT_dp_k

Solution

稍微接触过博弈论的都知道,要解决胜负问题,就要求必胜态与必败态,那么定义 \(sta_i\) 为石子数量只剩 \(i\) 时初始时的先手是否可以胜利。显然对于 \(i\in A\) 都有 \(sta_i=1\),那么假如某个状态 \(j\) 只可以通过状态 \(sta_k=1\) 转移,且 \(|j-k|\in A\),那么 \(j\) 就是必败态,否则是必胜态,所以 dp,启动!

Code

#include <bits/stdc++.h>
using namespace std;
int sta[int(1e5+10)],a[int(1e5+10)];
int main()
{
	int n,k;
	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]) sta[i]|=1-sta[i-a[j]];
		}
	}
	cout<<(sta[k]?"First":"Second");
	return 0;
}

AT_dp_l

Solution

\(dp_{l,r}\) 表示区间 \([l,r]\) 中最大的可以取到的 \(X-Y\),那么自然分奇偶性讨论,如果 \(len=r-l+1\) 是偶数,先手取,否则后手取,然后推一下转移方程就行了。一定要先枚举 \(l\) 再枚举 \(len\) 然后再计算 \(r\)

Code

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

AT_dp_m

Solution

先考虑最暴力的 dp,\(dp_{i,j}\) 表示前 \(i-1\) 个小朋友共分走了 \(j\) 颗糖的方案数,易得 \(dp_{i,j}=\displaystyle\sum_{k=\max(0,j-a_{i-1})}^jdp_{i-1,k}\),复杂度爆炸。

考虑优化它,由于涉及到和并且是静态的,想到前缀和优化 dp,于是有 \(sum_i=\displaystyle\sum_{j=0}^idp_{i-1,j}\),转移方程就得以优化。

Code

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

AT_dp_n

Solution

石子合并。

Code

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

AT_dp_o

Solution

\(n\) 特别小,状压 dp。考虑集合 \(S\) 表示当前被选择过的男人,用二进制表示,由于题目要求的是一对一对,所以就有同样数量的 \(|S|\) 个女人,每次加入一个新的男人,考虑连能连的女人的方案数,最后叠加即可。

Code

#include <bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int dp[1<<22],g[22][22];
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	cin>>g[i][j];
	dp[0]=1;
	for(int i=1;i<(1<<n);i++)
	{
		int sz=__builtin_popcount(i);
		for(int j=1;j<=n;j++)
		if(g[sz][j]&&(i>>j-1)&1)
		{
			dp[i]+=dp[i-(1<<j-1)];
			dp[i]%=mod;
		}
	}
	cout<<dp[(1<<n)-1];
	return 0;
}

AT_dp_p

Solution

跟没有上司的舞会很像,不同的是原题求最大快乐值,本题求方案数,所以转移应该是把所有儿子的 dp 值乘起来再取模。另外,dp 数组初值赋 \(1\)

Code

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+10;
const int mod=1e9+7;
vector<int> G[N];
int dp[N][2];
void dfs(int u,int fa)
{
	dp[u][0]=dp[u][1]=1;
	for(auto v:G[u])
	{
		if(v==fa) continue;
		dfs(v,u);
		dp[u][1]*=dp[v][0];
		dp[u][0]*=dp[v][1]+dp[v][0];
		dp[u][0]%=mod;
		dp[u][1]%=mod;
	}
}
signed main()
{
	int n;
	cin>>n;
	for(int i=1;i<n;i++)
	{
		int u,v;
		cin>>u>>v;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	dfs(1,0);
	cout<<(dp[1][0]+dp[1][1])%mod;
	return 0;
}

AT_dp_q

Solution

\(dp_i\) 表示前 \(i\) 朵花的最大价值,暴力的话有 \(dp_i=\max_{j=1}^{i-1}{dp_j(h_j<h_i)}+a_i\),显然超时。

注意到 \(h_i\le n\),考虑以 \(h_i\) 为下标开线段树,单点修改,区间求 \(\max\),这些都是基本操作,然后就做完了。

Code

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+10;
int maxx[N<<2],dp[N],h[N],a[N];
void update(int s,int t,int k,int v,int p)
{
	if(s==t&&t==k)
	{
		maxx[p]=v;
		return;
	}
	int m=s+t>>1;
	if(k<=m) update(s,m,k,v,p*2);
	else update(m+1,t,k,v,p*2+1);
	maxx[p]=max(maxx[p*2],maxx[p*2+1]);
}
int query(int l,int r,int s,int t,int p)
{
	if(l<=s&&t<=r) return maxx[p];
	int m=s+t>>1,ans=-1;
	if(l<=m) ans=max(ans,query(l,r,s,m,p*2));
	if(r>m) ans=max(ans,query(l,r,m+1,t,p*2+1));
	return ans;
}
signed main()
{
	int n,ans=-1;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>h[i];
	for(int i=1;i<=n;i++) cin>>a[i],dp[i]=a[i];
	for(int i=1;i<=n;i++)
	{
		dp[i]=query(1,h[i],1,n,1)+a[i];
		ans=max(ans,dp[i]);
		update(1,n,h[i],dp[i],1);
	}
	cout<<ans;
	return 0;
}

AT_dp_r

Solution

这题是典吧。把初始时的矩阵求 \(K\) 次方再把矩阵里的数求和就行了。至于证明,传递闭包应该能证。

Code

#include <bits/stdc++.h>
using namespace std;
long long n,k;
const long long mod=1e9+7;
struct matrix
{
	long long g[101][101],sz;
};
matrix unit()
{
	matrix ret;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	{
		if(i==j) ret.g[i][j]=1;
		else ret.g[i][j]=0;
	} 
	return ret;
}
matrix emp()
{
	matrix ret;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	ret.g[i][j]=0;
	return ret;
}
matrix times(matrix a,matrix b)
{
	matrix ans;
	ans=emp();
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	for(int l=1;l<=n;l++)
	ans.g[i][j]=(ans.g[i][j]+a.g[i][l]*b.g[l][j]%mod)%mod;
	return ans;
}
matrix mqpow(matrix b,long long p)
{
	matrix ret;
	ret=unit();
	while(p)
	{
		if(p&1) ret=times(ret,b);
		b=times(b,b);
		p>>=1ll;
	}
	return ret;
}
int main()
{
	cin>>n>>k;
	matrix a;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	cin>>a.g[i][j];
	matrix ans;
	long long ret=0;
	ans=mqpow(a,k);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		if(ans.g[i][j]) ret+=ans.g[i][j],ret%=mod;
	}
	cout<<ret;
	return 0;
}

AT_dp_s

Solution

双倍经验

看到 \(n\) 非常大,考虑数位 dp,其中一种数位 dp 的实现方法就是记搜。考虑 \(dp_{p,sum}\) 表示 \(p\) 位数里面小于 \(n\) 的前 \(p\) 位的数里有多少个数位和模 \(d\)\(sum\) 的数个数。那么考虑一个限制 \(limit\),当前位只能取 \(0\sim maxt\) 的数字,否则可能搜着搜着就比 \(n\) 大了,导致不方便统计答案,其中若 \(limit=0\)\(maxt=9\),否则是 \(n\) 的第 \(|n|=p\) 位。从高位往低位搜,并更新 \(sum,limit\)。其中 \(limit\) 的更新取决于搜下去时的数字是否等于 \(maxt\)

Code

#include <bits/stdc++.h>
using namespace std;
#define int long long
int dp[10010][110],d,k[10010];
string s;
const int mod=1e9+7;
int dfs(int p,int sum,int lim)
{
	if(!p) return (!sum);
	if(!lim&&~dp[p][sum]) return dp[p][sum];
	int maxt=lim?k[p]:9,ret=0;
	for(int i=0;i<=maxt;i++) ret=(ret+dfs(p-1,(sum+i)%d,lim&&i==maxt))%mod;
	if(!lim) dp[p][sum]=ret;
	return ret;
}
signed main()
{
	memset(dp,-1,sizeof dp);
	cin>>d>>s;
	for(int i=1;i<=s.size();i++) k[i]=s[s.size()-i]-'0';
	cout<<(dfs(s.size(),0,1)-1+mod)%mod<<'\n';
	return 0;
}

AT_dp_u

Solution

与 dp_o 的本质做法相似状压,设 \(i\) 为物品的集合,\(dp_i=\max_{j \subsetneq i}\{dp_j+dp_{i- j}\}\)

Code

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

AT_dp_t

Solution

观察到 \(O(n^2)\) 算法可以通过,想到二维 dp。设 \(dp_{i,j}\) 表示满足条件的 \(1\sim i\) 的排列且最后一个数为 \(j\) 的方案数,但是这个做法是 \(O(n^3)\) 的,前缀和优化即可。最终复杂度 \(O(n^2)\)

Code

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1e9+7;
int n,dp[3010][3010];//1~i的排列,结尾为j 
string s;
signed main()
{
	cin>>n>>s;
	dp[1][1]=1;
	for(int i=2;i<=n;i++)
	{
		int sum=0;
		if(s[i-2]=='<')
		{
			for(int j=1;j<=n;j++)
			{
				dp[i][j]=sum;
				sum+=dp[i-1][j];
				sum%=mod;
			}
		}
		else
		{
			for(int j=i-1;j;j--)
			{
				sum+=dp[i-1][j];
				sum%=mod;
				dp[i][j]=sum;
			}
		}
	}
	int sum=0;
	for(int i=1;i<=n;i++) sum+=dp[n][i],sum%=mod;
	cout<<sum<<endl;
	return 0;
}
//'<':dp[i][j]=sum{dp[i-1][k](1<=k<j)}
//'>':dp[i][j]=sum{dp[i-1][k](j<=k<i)}

AT_dp_x

Solution

教练讲过的贪心 dp 例题。首先看数据范围,\(w_i,s_i\) 都很小,联想到背包,但是本题有 \(s_i\) 的限制,所以不能是普通 01 背包,所以想到贪心。若箱子 \(i\)\(j\) 上,不一定因为 \(s_i>s_j\),而是 \(i\) 上除了放 \(j\),还要能放更多东西,于是贪心依据是 \(s_i-w_j<s_j-w_i\),移项得 \(s_i+w_i<s_j+w_j\),写进 cmp,排个序再 dp 就可以了。

\(dp_i\) 表示 \(i\) 质量下最大价值,转移方程和 01 背包相似,\(dp_{j+w_i}=\max{dp_j+v_i}\),倒序枚举 \(j\),注意 \(j\) 可以取到 \(0\)。最后输出 \(\max_{i=0}^{20000}dp_i\)

Code

#include <bits/stdc++.h>
using namespace std;
#define int long long
int dp[20010];
struct box
{
	int w,s,v;
}a[1010];
int cmp(box x,box y)
{
	return x.w+x.s<y.w+y.s;
}
signed main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i].w>>a[i].s>>a[i].v;
	memset(dp,-1,sizeof dp);
	dp[0]=0;
	sort(a+1,a+n+1,cmp);
	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);
	int ans=-1;
	for(int i=0;i<=2e4;i++) ans=max(ans,dp[i]);
	cout<<ans;
	return 0;
}
posted @ 2023-12-10 23:28  Crazyouth  阅读(49)  评论(0编辑  收藏  举报