「一本通」区间dp学习笔记

版权声明:本文为QQQQqtt原创,但是我猜没有人转载oAo https://blog.csdn.net/qq_36038511/article/details/82764474

总结:

一般是由长度小的子问题推到长度大的子问题,解法一般比较固定,先枚举长度再枚举左端点 最后枚举中间的分割点
有时候第一第二层分别枚举左端点和右端点 。看后效性 (靠感觉) 吧2333
时间复杂度: N 3 N^3 空间复杂度: N 2 N^2


loj#10147. 「一本通 5.1 例 1」石子合并

https://loj.ac/problem/10147
之前写过的:https://blog.csdn.net/qq_36038511/article/details/71023592

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int mn[210][210],mx[210][210];//[长度][左端点] 
int a[410],sum[410];
int main()
{
	memset(mn,63,sizeof(mn));
	memset(mx,0,sizeof(mx));
	int n;
	scanf("%d",&n);
	for (int i=1;i<=n;i++) {scanf("%d",&a[i]);a[i+n]=a[i];}
	for (int i=1;i<=2*n-1;i++) 
	{
		mn[1][i]=mx[1][i]=0; mn[0][i]=mn[0][i]=0;
		sum[i]=sum[i-1]+a[i];
	}
	for (int k=2;k<=n;k++)
	{
		for (int i=1;i<=2*n-1-k;i++)
		{
			int j=i+k-1;
			for (int u=i;u<j;u++)
			{
				mn[k][i]=min(mn[u-i+1][i]+mn[j-u][u+1]+sum[j]-sum[i-1],mn[k][i]);
				mx[k][i]=max(mx[u-i+1][i]+mx[j-u][u+1]+sum[j]-sum[i-1],mx[k][i]);
			}
		}
	}
	int minn=1e9,maxx=0;
	for (int i=1;i<=n;i++) 
	{
		minn=min(minn,mn[n][i]);
		maxx=max(maxx,mx[n][i]);
	}
	printf("%d\n%d\n",minn,maxx);
	return 0;
}

loj#10148. 「一本通 5.1 例 2」能量项链

https://loj.ac/problem/10148
思路&之前的题解同上

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
long long f[210][210],st[210],ed[210];
int main()
{
	int n;
	scanf("%d",&n);
	scanf("%lld%lld",&st[1],&ed[1]); st[n+1]=st[1]; ed[n+1]=ed[1];
	for (int i=2;i<=n-1;i++)
	{
		scanf("%lld",&ed[i]);
		st[i]=ed[i-1];
		st[i+n]=st[i]; ed[i+n]=ed[i];
	}
	st[n]=st[2*n]=ed[n-1];ed[n]=ed[n*2]=st[1];
	for (int k=2;k<=n;k++)//长度
	{
		for (int i=1;i<=2*n-k;i++)//左端点
		{
			int j=i+k-1;
			for (int u=i;u<j;u++)
			{
				f[k][i]=max(f[k][i],f[u-i+1][i]+f[j-u][u+1]+st[i]*ed[u]*ed[j]);
			}
		}
	}
	long long ans=0;
	for (int i=1;i<=n;i++) ans=max(ans,f[n][i]);
	printf("%lld\n",ans);
	return 0;
}

loj#10149. 「一本通 5.1 例 3」凸多边形的划分

https://loj.ac/problem/10149
通过画图可以知道 五边形可以由四边形/三边形推出,六边形可以由五边形/四边形/三边形推出……
给每个顶点标号1~n,f[i][j]表示跨越了i个顶点 起点为j 一样的套路
非常恶心的高精度

#include <cstdio>
#include <cstring>
#include <algorithm>
struct node
{
	int a[510],len;
	node()
	{
		memset(a,63,sizeof(a));
		len=99;
	}
}f[110][110],a[110];
//f[i][j]:顶点i到顶点j 的最小乘积之和 
using namespace std;
node chengfa(node x,node y)
{
	node c;
	c.len=x.len+y.len;
	memset(c.a,0,sizeof(c.a));
	for (int i=1;i<=x.len;i++)
	{
		for (int j=1;j<=y.len;j++)
		{
			c.a[i+j-1]+=x.a[i]*y.a[j];
		}
	}
	for (int i=1;i<=c.len;i++)
	{
		c.a[i+1]+=c.a[i]/10;
		c.a[i]%=10;
	}
	while (c.a[c.len]==0&&c.len>1) c.len--;
	return c;
}
node jiafa(node x,node y)
{
	node c;
	c.len=max(x.len,y.len)+1;
	memset(c.a,0,sizeof(c.a));
	for (int i=1;i<=c.len;i++)
	{
		c.a[i]=x.a[i]+y.a[i];
	}
	for (int i=1;i<=c.len;i++)
	{
		c.a[i+1]+=c.a[i]/10;
		c.a[i]%=10;
	}
	while (c.a[c.len]==0&&c.len>1) c.len--;
	return c;
}
void upd(node x,node &y)
{
	if (x.len<y.len) y=x;
	else if (x.len==y.len)
	{
		for (int i=x.len;i>=1;i--)
		{
			if (x.a[i]<y.a[i]) {y=x; break;}
			else if (x.a[i]>y.a[i]) {break;}
		}
	}
}
int main()
{
	int n;
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
	{
		int x;
		memset(a[i].a,0,sizeof(a[i].a)); a[i].len=0;
		scanf("%d",&x);
		while (x!=0)
		{
			a[i].a[++a[i].len]=x%10;
			x/=10;
		}
		a[i+n]=a[i];
	}
	for (int i=1;i<=n;i++)
	{
		memset(f[i][i].a,0,sizeof(f[i][i])); f[i][i].len=0;
		memset(f[i][i+1].a,0,sizeof(f[i][i+1]));f[i][i+1].len=0;
		memset(f[i-1][i].a,0,sizeof(f[i-1][i]));f[i-1][i].len=0;
	}
	for (int l=2;l<n;l++)
	{
		for (int i=1;i<=n-l;i++)
		{
			int j=i+l; 
			for (int k=i+1;k<j;k++)
			{
				node ma;
				ma=chengfa(a[i],a[k]);
				ma=chengfa(ma,a[j]);
				node ad;
				ad=jiafa(f[i][k],f[k][j]); 
				ad=jiafa(ad,ma);
				upd(ad,f[i][j]);
			}
		}
	}
	for (int i=f[1][n].len;i>=1;i--)
	{
		printf("%d",f[1][n].a[i]);
	}
	printf("\n");
	return 0;
}

loj#10150. 「一本通 5.1 练习 1」括号配对

https://loj.ac/problem/10150
第一眼看成之前做进阶的时候的括号画家2333
一开始写了预处理括号然后区间dp 但是还是too young too simple了
([%@*!]) 可以把中间的处理掉之后直接加上两层括号啊
注意 ([)] 这种样例,注意两种括号的配对 “(” 不能对"]" (错的都是什么沙雕错误)

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int f[110][110],a[110];
char ch[110]; 
int main()
{
	memset(f,63,sizeof(f));
	scanf("%s",ch+1);
	int n=strlen(ch+1);
	for (int i=1;i<=n;i++)
	{
		if (ch[i]=='(') a[i]=-1;
		else if (ch[i]==')') a[i]=1;
		else if (ch[i]=='[') a[i]=-2;
		else if (ch[i]==']') a[i]=2;
		f[1][i]=1; f[0][i]=0; 
	}
	for (int k=2;k<=n;k++)
	{
		for (int i=1;i<=n-k+1;i++)
		{	
			int j=i+k-1;
			for (int u=i;u<=j;u++)
			{
				f[k][i]=min(f[k][i],f[u-i+1][i]+f[j-u][u+1]);
			}
			if (a[i]+a[j]==0&&a[i]<0) f[k][i]=min(f[k][i],f[k-2][i+1]);
		}
	}
	printf("%d\n",f[n][1]);
	return 0;
}

loj#10151. 「一本通 5.1 练习 2」分离与合体

https://loj.ac/problem/10151
这题的题意本身是难点…读懂题花了一下午
感性的理解一下:倒过来做,从最后合体的那个的开始往两边扩张,往回推分离的过程
要分离到不能分离才合体!
至于方案…写个pre然后倒搜
不要学我写高精度!!

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
struct node
{
	long long s; 
	int p;
	node()
	{
		s=0; p=0;
	}
}f[310][610];
int a[310],sum[310];
struct nodel
{
	int k,i,p;
}list[610];
int main()
{
	int n;
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		f[1][i].s=0;f[1][i].p=i;
		//sum[i]=a[i]+sum[i-1];
	}
	for (int k=2;k<=n;k++)
	{
		for (int i=1;i<=n-k+1;i++)
		{
			int j=i+k-1;
			for (int u=i;u<j;u++)
			{
				if (f[u-i+1][i].s+f[j-u][u+1].s+(a[i]+a[j])*a[u]>f[k][i].s)
				{
					f[k][i].s=f[u-i+1][i].s+f[j-u][u+1].s+(a[i]+a[j])*a[u];
					f[k][i].p=u;
				}
			}
		}
	}
	printf("%lld\n",f[n][1]); 
	int head=1,tail=1,k=n-1;
	list[1].p=f[n][1].p; list[1].k=n; list[1].i=1; 
	while (head<=tail)
	{
		if (list[head].k<=2||list[head].i==0) {head++; continue;}
		int p=f[list[head].k][list[head].i].p;
		list[++tail].k=p-list[head].i+1;
		list[tail].i=list[head].i;
		list[tail].p=f[list[tail].k][list[tail].i].p;
		list[++tail].k=list[head].i+list[head].k-1-p;
		list[tail].i=p+1;
		list[tail].p=f[list[tail].k][list[tail].i].p;
		head++;
	}
	for (int i=1;i<=tail;i++)
		if (list[i].k>=2) printf("%d ",list[i].p);
	printf("\n");
	return 0;
}

loj#10152. 「一本通 5.1 练习 3」矩阵取数游戏

https://loj.ac/problem/10152
一开始还想贪心直接取两端的最小值但是第二个样例就卡掉了……
跟上一题一样的骚操作:对于每一行,相当于从最后结束的那个数向两边扩展 因为每次只用选一个数 不用枚举分割点

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
struct node
{
	int len,a[100];
	node()
	{
		memset(a,0,sizeof(a));
		len=0;
	}
}f[90][90][90],pi[90];
int a[90][90];
node chengfa(node x,int y)
{
	node c; c.len=x.len;
	for (int i=1;i<=x.len;i++)
	{
		c.a[i]=x.a[i]*y;
	}
	for (int i=1;i<=c.len;i++)
	{
		if (c.a[i]>9)
		{
			c.a[i+1]+=c.a[i]/10;
			c.a[i]%=10;
			if (i==c.len) c.len++;
		}
	}
	while (c.len>1&&c.a[c.len]==0) c.len--;
	return c;
}
node jiafa(node x,node y)
{
	node c;c.len=max(x.len,y.len)+1;
	for (int i=1;i<=c.len;i++)
		c.a[i]=x.a[i]+y.a[i];
	for (int i=1;i<=c.len;i++)
	{
		c.a[i+1]+=c.a[i]/10;
		c.a[i]%=10;
	}
	while (c.len>1&&c.a[c.len]==0) c.len--;
	return c;
}
node cpr(node x,node y)
{
	if (x.len>y.len) return x;
	else if (x.len<y.len) return y;
	else 
	{
		for (int i=x.len;i;i--)
		{
			if (x.a[i]>y.a[i]) return x;
			else if (x.a[i]<y.a[i]) return y;
		}
	}
	return x;
}
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	pi[0].a[1]=1; pi[0].len=1;
	for (int i=1;i<=m;i++) pi[i]=chengfa(pi[i-1],2);
	for (int i=1;i<=n;i++)
	{
		for (int j=1;j<=m;j++)
		{
			scanf("%d",&a[i][j]);
		}
	}
	node ans;
	for (int ki=1;ki<=n;ki++)
	{
		for (int k=1;k<=m;k++)
		{
			for (int i=1;i<=m-k+1;i++)
			{
				int j=i+k-1;
				node s1=jiafa(f[ki][k-1][i+1],chengfa(pi[m-k+1],a[ki][i]));
				node s2=jiafa(f[ki][k-1][i],chengfa(pi[m-k+1],a[ki][j]));
				node s=cpr(s1,s2);
				f[ki][k][i]=cpr(f[ki][k][i],s);
			}
		}
		ans=jiafa(ans,f[ki][m][1]);
	}
	for (int i=ans.len;i;i--)
	{
		printf("%d",ans.a[i]);
	}
	return 0;
}

完结撒花!

猜你喜欢

转载自blog.csdn.net/qq_36038511/article/details/82764474