20261001 DP专题

toasting

\(n\) 个人围成一圈,每个人有一权值 \(c_i\),将他们两两连线,求在线不交叉的情况下最多有多少对线两端的人的权值相等。

  • 一种情况:\(l\) 和 \(r\) 连线,他们之间的最大配对数都能由他们之间的人,也就是 \(l+1\) 到 \(r-1\) 的人的配对数量得来。因为 \(l\) 和 \(r\) 连线之后必然不与其之间的任何线相交。具体来说,如果 \(l\) 和 \(r\) 的权值 \(c\) 不同,那么他们之间的最大配对数为 \(l+1\) 和 \(r-1\) 之间的最大配对数;否则若相等,就是 \(l+1\) 和 \(r-1\) 之间的最大配对数加一;

  • 另一种情况:在 \(l\) 到 \(r\) 之间枚举割点 \(k\),考虑 \(l\) 到 \(k\) 互相配对,\(k+1\) 到 \(r\) 互相配对,\(l\) 到 \(r\) 区间的答案即为两边之和;

#include<bits/stdc++.h>
using namespace std;
int a[2003],dp[2003][2003];
int main(){
	freopen("toasting.in","r",stdin);
	freopen("toasting.out","w",stdout);
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),a[n+i]=a[i];
	if(n%2!=0){
		printf("0\n");
		return 0;
	}
	for(int i=1;i<=2*n;i++)
		if(a[i]==a[i+1]) dp[i][i+1]=1;
	for(int len=2;len<=n;len++)
		for(int l=1;l<=n;l++){
			int r=l+len-1;
			dp[l][r]=dp[l+1][r-1]+(a[l]==a[r]);
			for(int k=l+1;k<r;k+=2) dp[l][r]=max(dp[l][r],dp[l][k]+dp[k+1][r]);
		}
	int ans=0;
	for(int i=1;i<=n;i++) ans=max(ans,dp[i][i+n-1]);
	printf("%d\n",ans);
	return 0;
}

strawberry

一棵有根树,节点有权值,判断有没有拆除任意两条边的方案,使得三部分的权值和相等。

记 \(k\) 为权值总和的三分之一。

  • 一种情况:如果当前结点 \(i\) 的儿子中,有其中两个儿子 \(s1\),\(s2\),他们的子树中都有权值为 \(k\) 的节点,此时切掉s1和s2的父边为一个可行方案。

  • 另一种情况:如果当前结点不是根,子树和恰好为 \(2*k\),并且其子树中存在权值为 \(k\) 的子树。此时切掉其父边,以及它某个后代的父边,形成一个可行的方案。

注意特判 \(k\) 不为整数和全零的情况。

#include<bits/stdc++.h>
using namespace std;
vector<int> tr[100003];
int val[100003],sum[100003],fa[100003],flag[100003];
bool tag=false;
void work(int u){
	sum[u]=val[u];
	for(int i:tr[u]){
		if(i==fa[u]) continue;
		work(i);
		sum[u]+=sum[i];
	}
	return;
}
void check(int u,int pur,int ls){
	if(sum[u]==2*pur) ls=1;
	else if(sum[u]==pur){
		flag[u]=1;
		if(ls==1){
			tag=true;
			return;
		}
	}
	int opt=0;
	for(int i:tr[u]){
		if(i==fa[u]) continue;
		check(i,pur,ls);
		if(flag[i]==1) opt++,flag[u]=1;
	}
	if(opt>=2) tag=true;
	return;
}
int main(){
	freopen("strawberry.in","r",stdin);
	freopen("strawberry.out","w",stdout);
	int t;
	scanf("%d",&t);
	while(t--){
		memset(flag,0,sizeof(flag));
		int n,root;
		scanf("%d",&n);
		for(int i=1;i<=n;i++) tr[i].clear();
		for(int i=1;i<=n;i++){
			scanf("%d%d",&fa[i],&val[i]);
			if(fa[i]==0) root=i;
			else tr[i].push_back(fa[i]),tr[fa[i]].push_back(i);
		}
		work(root);
		tag=false;
		check(root,sum[root]/3,0);
		if(sum[root]==0){
			if(n>=3) printf("YES\n");
			else printf("NO\n");
		}
		else if(sum[root]%3!=0) printf("NO\n");
		else if(tag==true) printf("YES\n");
		else printf("NO\n");
	}
	return 0;
}

meaning

两个字符串 \(s\) 和 \(t\),其中 \(t\) 有两个意思,求 \(s\) 总共有多少种意思,\(t\) 不能重叠。

答案对 \(1e9+7\) 取模。

  • \(KMP\) 求 \(s\) 中所有 \(t\) 子串;

  • \(dp[i]\) 表示 \(s\) 前 \(i\) 位的意思数。若 \(t\) 与 \(s\) 匹配的结束位置为 \(i\),那么 \(dp[i]\) 为 \(dp[i-1]\) 加上 \(dp[i-lent]\);否则为 \(dp[i-1]\);

#include<bits/stdc++.h>
using namespace std;
int bor[100003],same[100003],dp[100003];
const int mod=1e9+7;
int main(){
	freopen("meaning.in","r",stdin);
	freopen("meaning.out","w",stdout);
	int n;
	cin>>n;
	while(n--){
		string s,t;
		cin>>s>>t;
		int lens=s.length(),lent=t.length();
		s='*'+s,t='*'+t;
		memset(bor,0,sizeof(bor));
		memset(same,0,sizeof(same));
		bor[1]=0;
		int p=0;
		for(int i=2;i<=lent;i++){
		    while(p>0&&t[i]!=t[p+1]) p=bor[p];
		    if(t[i]==t[p+1]) p++;
		    bor[i]=p;
		}
		int ids=1,idt=0,tot=0;
		while(ids<=lens){
			if(s[ids]==t[idt+1]) ids++,idt++;
			else if(idt!=0) idt=bor[idt];
			else ids++;
			if(idt==lent){
				same[++tot]=ids-1;
				idt=bor[idt];
			}
		}
		dp[0]=1;
		int nowid=1;
		for(int i=1;i<=lens;i++){
			dp[i]=dp[i-1];
			if(nowid<=tot&&i==same[nowid]) dp[i]=(dp[i]+dp[i-lent])%mod,nowid++;
		}
		cout<<dp[lens]%mod<<endl;
	}
	return 0;
}

fibonacci

一个序列,求其中最长斐波那契子序列长度。

其中“斐波那契子序列”指序列中每个数都等于前两个之和。

  • \(O(n^3)\) 的 \(dp\) 非常好想,考虑如何优化。

  • “用空间换时间”,开一个 \(map\) 记录原序列中数最早出现的位置。倒序遍历原序列,一边遍历一边记录当前最早出现的位置。

总之非常好理解,看代码就懂了。

#include<bits/stdc++.h>
using namespace std;
int dp[3003][3003],a[3003];
map<int,int> mp[3003];
int main(){
	freopen("fibonacci.in","r",stdin);
	freopen("fibonacci.out","w",stdout);
	int n,ans=0;
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)
		for(int j=i+1;j<=n;j++) dp[i][j]=2;
	for(int i=n;i>=1;i--){
		for(int j=i+1;j<=n;j++){
			if(mp[j+1].count(a[i]+a[j])) dp[i][j]=max(dp[i][j],dp[j][mp[j+1][a[i]+a[j]]]+1),ans=max(ans,dp[i][j]);
		}
		mp[i]=mp[i+1],mp[i][a[i]]=i;
	}
	printf("%d\n",ans);
	return 0;
}
posted @ 2026-10-01 17:41  404-NoT_FinD  阅读(8)  评论(0)    收藏  举报