DP专题

66 阅读2分钟

挖地雷

题目链接

思路

起点必定是入度为00的点。假设现在选了一个入度非00的点uu,因为点权均为正值,所以选一个通过有向边连接uu的点作为起点更优。用拓扑排序,更新到达第ii个点能够获得的最大价值并记录路径。

代码

#include<bits/stdc++.h>
#define rep(i,st,ed) for(int (i)=(st);(i)<=(ed);++(i))
#define bl(u,i) for(int (i)=head[(u)];(i);(i)=e[(i)].nxt)
#define sf(a) scanf("%lld",&(a));
#define pf(a) printf("%lld\n",(a));
typedef long long ll;
using namespace std;
const int N=24,INF=0x3f3f3f3f;
int n,tot;
int head[N],a[N],pre[N],f[N],in[N];
queue<int> q;
struct Edge{
	int nxt,to;
}e[N*N];
inline void add_edge(int u,int v,int flag)
{
	e[++tot].nxt=head[u];
	e[tot].to=v;
	head[u]=tot;
	if(flag)
		add_edge(v,u,0);
}
void topo()
{
	rep(i,1,n)
	{
		if(in[i]==0)
		{
			q.push(i);
			f[i]=a[i];
		}
	}
	while(!q.empty())
	{
		int u=q.front();q.pop();
		bl(u,i)
		{
			int v=e[i].to;
			if(f[v]<f[u]+a[v])
			{
				f[v]=f[u]+a[v];
				pre[v]=u;
			}
			--in[v];
			if(in[v]==0)
				q.push(v);
		}
	}
}
void dfs(int x)
{
	if(!pre[x])
	{
		cout<<x<<" ";
		return;
	}
	dfs(pre[x]);
	cout<<x<<" ";
}
int main()
{
	cin>>n;
	rep(i,1,n)
	{
		cin>>a[i];
	}
	rep(i,1,n-1)
	{
		rep(j,1,n-i)
		{
			int is;
			cin>>is;
			if(is)
			{
				add_edge(i,i+j,0);
				++in[i+j];
			}
		}
	}
	topo();
	int p=0;
	rep(i,1,n)
	{
		if(f[i]>f[p])
		{
			p=i;
		}
	}
	dfs(p);
	cout<<endl;
	cout<<f[p]<<endl;
}

五倍经验日

题目链接

思路

先把输掉拿的经验维护进答案,现在开始嗑药,这个问题就变成一个典型的0101背包问题,容量为xx,第ii个物品的价值是win[i]lose[i]win[i]-lose[i],代价是use[i]use[i]

代码

#include<bits/stdc++.h>
#define rep(i,st,ed) for(int (i)=(st);(i)<=(ed);++(i))
#define bl(u,i) for(int (i)=head[(u)];(i);(i)=e[(i)].nxt)
#define sf(a) scanf("%lld",&(a));
#define pf(a) printf("%lld\n",(a));
typedef long long ll;
using namespace std;
const int N=1004;
ll n,x,ans;
ll w[N],f[N],v[N];
int main()
{
	scanf("%lld%lld",&n,&x);
	rep(i,1,n)
	{
		ll x,y;
		cin>>x>>y>>w[i];
		ans+=x;
		v[i]=y-x;
	}
	rep(i,1,n)
	{
		for(int j=x;j>=w[i];--j)
		{
			f[j]=max(f[j],f[j-w[i]]+v[i]);
		}
	}
	cout<<(ans+f[x])*5ll<<endl;
}

最长公共子序列

题目链接

思路

通常求最长公共子序列复杂度为O(n2)O(n^2),对于这个题来说太慢了。注意到所给出的数组是全排列,也就是说AABB数组的长度和元素都相同,只有元素出现位置的差异。所以AA中的每一个元素都可以在BB中找到和自己相等的一个。把如果AA中有一个序列,他们中的每个元素在BB中的出现位置是单调递增的(也就是从左到右依次出现),那么这个序列必定是AABB的一个公共子序列。
考虑把AA中的元素换成该元素在BB中出现的位置,那就把问题转化成了求AA的最长上升子序列(LISLIS)。求解这个问题有O(nlogn)O(n\log n)的算法。扫一遍AA,设当前的LISLISqq,大小为cntcnt。如果a[i]>q[cnt]a[i]>q[cnt],就直接把a[i]a[i]加到qq中。否则就通过二分查找(因为qq是有序的),找到qq中第一个大于a[i]a[i]的元素将其替换为a[i]a[i]
正确性的证明:
第一种情况很显然是正确的,因为这样做会让当前LISLIS的长度增加,得到一个更优的解。
第二种情况下,我们将当前LISLIS中一个较大的数替换成一个较小的数,这样并不影响当前LISLIS的长度,但是却可以让LISLIS在之后更可能变长。记替换位置为uu,如果uu之后的数字在之后被替换掉了,那么当前LISLIS的最后一个数字一定比原来小,就更可能接受之后的数字作为最大值,从而使答案更优。如果uu之后的数字没有被替换掉,LISLIS的长度仍然正确,因此这样做就可能会让答案更优。

代码

#include<bits/stdc++.h>
#define rep(i,st,ed) for(int (i)=(st);(i)<=(ed);++(i))
#define bl(u,i) for(int (i)=head[(u)];(i);(i)=e[(i)].nxt)
#define sf(a) scanf("%lld",&(a));
#define pf(a) printf("%lld\n",(a));
typedef long long ll;
using namespace std;
const int N=1E5+10,INF=0x3f3f3f3f;
int n;
int b[N],p[N],q[N];
int main()
{
	cin>>n;
	rep(i,1,n)
	{
		int tmp;
		scanf("%d",&tmp);
		p[tmp]=i;
	}
	rep(i,1,n)
	{
		int tmp;
		scanf("%d",&tmp);
		b[i]=p[tmp];
	}
	int cnt=0;
	rep(i,1,n)
	{
		if(q[cnt]<b[i])
			q[++cnt]=b[i];
		else
		{
			int pos=lower_bound(q+1,q+cnt+1,b[i])-q;
			q[pos]=b[i];
		}
	}
	cout<<cnt<<endl;;
}

关于路径的记录:参考博客
每次用b[i]b[i]更新队列,都记录一下它在队列中所处的位置,最后从出现位置为队列长度(cntcnt)的元素开始往前找,以它为结尾的LISLIS倒数第二个元素就是出现位置为cnt1cnt-1的元素。以此类推,就可以得到倒序的LISLIS

编辑距离

题目链接

思路

f[i][j]f[i][j]AA的前ii个字符转化成BB的前jj个字符的编辑距离。状态间的转移有四种操作,分类讨论:

  • 删除:f[i][j]=f[i1][j]+1f[i][j]=f[i-1][j]+1
  • 插入:f[i][j]=f[i][j1]+1f[i][j]=f[i][j-1]+1
  • 更改:f[i][j]=f[i1][j1]+1f[i][j]=f[i-1][j-1]+1
  • 不变:f[i][j]=f[i1][j1]f[i][j]=f[i-1][j-1] 在其中取最小值维护f[i][j]f[i][j]即可。

代码

#include<bits/stdc++.h>
#define rep(i,st,ed) for(int (i)=(st);(i)<=(ed);++(i))
#define bl(u,i) for(int (i)=head[(u)];(i);(i)=e[(i)].nxt)
#define sf(a) scanf("%lld",&(a));
#define pf(a) printf("%lld\n",(a));
typedef long long ll;
using namespace std;
const int N=2005,INF=0x3f3f3f3f;
string a,b;
int f[N][N];
int main()
{
	cin>>a>>b;
	a=' '+a;
	b=' '+b;
	int n=a.size()-1,m=b.size()-1;
	memset(f,INF,sizeof(f));
	rep(i,0,m)
		f[0][i]=i;
	rep(i,0,n)
		f[i][0]=i;
	rep(i,1,n)
	{
		rep(j,1,m)
		{
			if(a[i]==b[j])
				f[i][j]=min(f[i][j],f[i-1][j-1]);
			else
			{
				f[i][j]=min(f[i][j],f[i][j-1]+1);//add
				f[i][j]=min(f[i][j],f[i-1][j]+1);//del
				f[i][j]=min(f[i][j],f[i-1][j-1]+1);//replace
			}
		}
	}
	cout<<f[n][m]<<endl;
}

大师

题目链接

思路

f[i][j]f[i][j]为以第ii个元素结尾,公差为jj的方案个数。那么ii前面的每一个元素kk都可以作为这个等差数列的倒数第二项,公差为h[i]h[k]h[i]-h[k],因为公差可为负数,因此加一个较大的常数(最大高度HH)防止出现负号下标。
c=h[i]h[k]+Hc=h[i]-h[k]+H,则f[i][c]=f[k][c]+1f[i][c]=f[k][c]+1(第ii个元素可以与即成的所有等差数列合并,数目不变,另外还可以与第kk个形成一个新的等差数列,因此+1+1),每次更新f[i][c]f[i][c]时都用f[k][c]+1f[k][c]+1维护答案。因为单独一个元素也可作为等差数列,因此答案还要加nn

代码

#include<bits/stdc++.h>
#define rep(i,st,ed) for(int (i)=(st);(i)<=(ed);++(i))
#define bl(u,i) for(int (i)=head[(u)];(i);(i)=e[(i)].nxt)
#define sf(a) scanf("%lld",&(a));
#define pf(a) printf("%lld\n",(a));
typedef long long ll;
using namespace std;
const int N=1002,H=2E4+2,M=998244353;
int n,ans;
int a[N],f[N][2*H];
int calc(int x)
{
	int ret=(x-1)*(x-2)>>1;
	return ret%M;
}
int main()
{
	scanf("%d",&n);
	rep(i,1,n)
		scanf("%d",a+i);
    ans=n;
	rep(i,1,n)
	{
		rep(j,1,i-1)
		{
			f[i][a[i]-a[j]+H]+=f[j][a[i]-a[j]+H]+1;
			f[i][a[i]-a[j]+H]%=M;
			ans=(ans+f[j][a[i]-a[j]+H]+1)%M;
		}
	}
	cout<<ans<<endl;
}

摆花

题目链接

思路

f[i][j]f[i][j]为前ii种花,放jj盆的方案数。枚举第ii种花放k(0kmin(a[i],j)k(0\leq k\leq min(a[i],j)盆,则f[i][j]=Σk=0min(a[i],j)(f[i1][jk])f[i][j]=\Sigma_{k=0}^{min(a[i],j)} (f[i-1][j-k])
关于边界的初始化:只放第一种花,不管放多少盆(不超过a[i]a[i])都只有一种方案,因此f[1][i]=1(i[0,a[1]])f[1][i]=1(i\in[0,a[1]])。放0盆花,不管是前几种都只有一种方案,因此f[i][0]=1(i[1,n])f[i][0]=1(i\in[1,n])。但其实这些状态都可以由f[0][0]f[0][0]直接或间接转移过去,因此初始化f[0][0]=1f[0][0]=1即可。
滚动数组优化:计算前ii种花时,只会用到前i1i-1种的数据,因此可以滚动数组优化。

代码

#include<bits/stdc++.h>
#define rep(i,st,ed) for(int (i)=(st);(i)<=(ed);++(i))
#define bl(u,i) for(int (i)=head[(u)];(i);(i)=e[(i)].nxt)
#define sf(a) scanf("%lld",&(a));
#define pf(a) printf("%lld\n",(a));
typedef long long ll;
using namespace std;
const int N=104,M=1E6+7;
int n,m,ans;
int a[N],f[N];
int main()
{
	scanf("%d%d",&n,&m);
	rep(i,1,n)
		scanf("%d",a+i);
    f[0]=1;
	rep(i,1,n)
	{
	    for(int j=m;j>=0;--j)
		{
			rep(k,1,min(j,a[i]))
			{
				f[j]+=f[j-k];
				f[j]%=M;
			}
		}
	}
	cout<<f[m]<<endl;
}