动归杂题冲刺三十题

背景:

在被DP一顿捶打的悲惨经历之后,我打算开始反击了!!!不能让DP对我再痛下杀手了!要起义了,,,(假装很大声)所以我准备了30道DP题目,(先立个小目标,在11月之前刷完,然而现在已经24号了,,,,)希望能唤起我对于写DP的灵感,毕竟CSP历年来考的重点在于DP和图论啊,(表示学了半年的数据结构,,哭死),为了记录和监督自己,决定写博客进行记录!
(该成30的原因见下面T24,,,表示真的是时间原因,,,实则是因为菜/(ㄒoㄒ)/~~)

题目:

T1:

P1508 Likecloud-吃、吃、吃

这题,真的,,,不说了,刚开始就想是要处理一下刚开始三角形的走法走不到的地方,然后就一直绕来绕去,所以就很耽误时间,但回头看一下题解,发现到最后直接
m a x ( f [ n ] [ m / 2 + 2 ] , f [ n ] [ m / 2 + 1 ] , f [ n ] [ m / 2 ] ) max(f[n][m/2+2],f[n][m/2+1],f[n][m/2]) max(f[n][m/2+2],f[n][m/2+1],f[n][m/2])就可以解决了,,,还有就是没有正着搜,我就直接想的模拟,,我真的,,服了。。。
f [ i ] [ j ] : f[i][j]: f[i][j]: 表示到第i,j个位置的时候的最大答案。
转移方程:
f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j + 1 ] , f [ i − 1 ] [ j − 1 ] ) f[i][j]=max(f[i-1][j],f[i-1][j+1],f[i-1][j-1]) f[i][j]=max(f[i1][j],f[i1][j+1],f[i1][j1])
答案:
a n s = m a x ( f [ n ] [ m / 2 + 2 ] , f [ n ] [ m / 2 + 1 ] , f [ n ] [ m / 2 ] ) ans=max(f[n][m/2+2],f[n][m/2+1],f[n][m/2]) ans=max(f[n][m/2+2],f[n][m/2+1],f[n][m/2])

代码:

#include<bits/stdc++.h>
//#define int long long
#define D double 
#define mp(s,t) make_pair(s,t)
#define pii pair<int,int>
#define fir first
#define sec second
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int ocean=0x7fffffff;
const int mod=1e9+7;
const int sea=1e6+7;
const int pool=1100;
int n,m,ans,a[pool][pool],f[pool][pool]; 
int main()
{
	n=read(); m=read();
	over(i,1,n) over(j,1,m) a[i][j]=read();
	over(i,1,n) a[i][0]=-ocean; over(i,1,n) a[i][m+1]=-ocean;
	over(i,1,m) f[n][i]=a[n][i];
	over(i,1,n) over(j,1,n)
	f[i][j]=max(f[i-1][j-1],max(f[i-1][j],f[i-1][j+1]))+a[i][j];
	ans=max(f[n][m/2+2],max(f[n][m/2+1],f[n][m/2]));
	printf("%d\n",ans);
	return 0;
}

T2:

P1387 最大正方形

这个题,刚开始是没有一点点思路的,,然后就看了题解,,,题解是真的巧妙!!!
f [ i ] [ j ] : f[i][j]: f[i][j]:以i,j为右下角的时候所构成的最大的正方形的边长。
状态转移方程:
f [ i ] [ j ] = m i n ( f [ i ] [ j − 1 ] , f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − 1 ] ) + 1 f[i][j]=min(f[i][j-1],f[i-1][j],f[i-1][j-1])+1 f[i][j]=min(f[i][j1],f[i1][j],f[i1][j1])+1
答案:
a n s = m a x ( f [ i ] [ j ] ) ans=max(f[i][j]) ans=max(f[i][j])

代码:

#include<bits/stdc++.h>
//#define int long long
#define D double
#define mp(s,t) make_pair(s,t)
#define pii pair<int,int>
#define fir first
#define sec second
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int ocean=0x7fffffff;
const int mod=1e9+7;
const int sea=1e6+7;
const int pool=1100;
int n,m,ans,a[pool][pool],f[pool][pool]; 
int main()
{
	n=read(); m=read(); over(i,1,n) over(j,1,m) a[i][j]=read(); memset(f,0,sizeof f); 
	over(i,1,n) over(j,1,m){if(a[i][j]) f[i][j]=min(min(f[i-1][j],f[i][j-1]),f[i-1][j-1])+1;ans=max(ans,f[i][j]);}
	printf("%d\n",ans);
	return 0;
}

T3:

P1417 烹调方案

这个题我刚开始看就是贪心,然后就是贪心地去排序,然后,,,就得到了5分的好成绩,还是好好写DP吧,想到了01背包的写法,但是好像直接写01包是不能保证最优性,所以就去看了题解的,才发现如果这两者结合就好了,然后还用滚动数组压了一维。
f [ i ] : f[i]: f[i]:表示容积为i的时候的最优答案。
状态转移方程:
f [ j + x [ i ] . c ] = m a x ( f [ j ] + x [ i ] . a − x [ i ] . b ∗ ( j + x [ i ] . c ) ) ( j + x [ i ] . c < = V ) f[j+x[i].c]=max(f[j]+x[i].a-x[i].b*(j+x[i].c)) (j+x[i].c<=V) f[j+x[i].c]=max(f[j]+x[i].ax[i].b(j+x[i].c))(j+x[i].c<=V)
答案:
a n s = m a x ( f [ i ] ) ans=max(f[i]) ans=max(f[i])

代码:

#include<bits/stdc++.h>
#define int long long
#define D double
#define mp(s,t) make_pair(s,t)
#define pii pair<int,int>
#define fir first
#define sec second
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
   int s=0,w=1;char ch=getchar();
   while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
   while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
   return s*w;
}
const int ocean=0x7fffffff;
const int mod=1e9+7;
const int sea=1e6+7;
const int pool=1100;
struct hit{int a,b,c;}x[sea]; 
int V,n,ans,ins,f[sea];
bool cmp(hit x,hit y){return x.c*y.b<x.b*y.c;}
signed main()
{
   V=read(); n=read();over(i,1,n) x[i].a=read();over(i,1,n) x[i].b=read();over(i,1,n) x[i].c=read();
   sort(x+1,x+1+n,cmp); over(i,1,n) f[i]=-ocean; f[0]=0; 
   over(i,1,n)	lver(j,V,0) if(j+x[i].c<=V)
   f[j+x[i].c]=max(f[j+x[i].c],f[j]+x[i].a-x[i].b*(j+x[i].c));
   over(i,1,V) ans=max(ans,f[i]);  printf("%lld\n",ans);
   return 0;
}

T4:

P1855 榨取kkksc03

这个题,,刚开始就想到了开两维的01背包,但是又一次不知道为什么就走到了贪心的道路上,就又跑偏了,,,辛亏到最后又想回来了,,打算去再把01包的板子敲一下,,
f [ i ] [ j ] : f[i][j]: f[i][j]:表示体积为i,时间为j的时候的最优个数
状态方程转移式:
f [ i ] [ j ] = m a x ( f [ i − a [ i ] . v ] [ j − a [ i ] . t ] ) f[i][j]=max(f[i-a[i].v][j-a[i].t]) f[i][j]=max(f[ia[i].v][ja[i].t])
答案: f [ V ] [ T ] f[V][T] f[V][T]

代码:

#include<bits/stdc++.h>
//#define int long long
#define D double
#define mp(s,t) make_pair(s,t)
#define pii pair<int,int>
#define fir first
#define sec second
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int ocean=0x7fffffff;
const int mod=1e9+7;
const int sea=1e6+7;
const int pool=1100;
struct hit{int v,t;}a[pool];
int n,V,T,ans,f[pool][pool];
int main()
{
	n=read(); V=read(); T=read(); over(i,1,n) a[i].t=read(), a[i].v=read();
	over(i,1,n) over(j,1,n) f[i][j]=-ocean; f[0][0]=0;
	over(i,1,n) lver(j,V,a[i].v) lver(k,T,a[i].t)
	f[j][k]=max(f[j][k],f[j-a[i].v][k-a[i].t]+1);
	printf("%d\n",f[V][T]);
	return 0;
}

T5:

P1541 乌龟棋
这个题,写完背包之后就应该能磕出来了吧,运用背包的思想,直接开四维即可。
f [ i ] [ j ] [ k ] [ l ] : f[i][j][k][l]: f[i][j][k][l]:表示牌号分别为1,2,3,4,的时候用了i,j,k,l张牌
状态转移方程:
(这个由于太长了就直接看代码吧)
答案:
f [ b [ 1 ] ] [ b [ 2 ] ] [ b [ 3 ] ] [ b [ 4 ] ] f[b[1]][b[2]][b[3]][b[4]] f[b[1]][b[2]][b[3]][b[4]]

#include<bits/stdc++.h>
//#define int long long
#define D double
#define mp(s,t) make_pair(s,t)
#define pii pair<int,int>
#define fir first
#define sec second
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int ocean=0x7fffffff;
const int mod=1e9+7;
const int sea=50;
const int pool=1100;
int n,m,ans,f[sea][sea][sea][sea],a[pool],b[sea];
int main()
{
	n=read(); m=read(); over(i,1,n) a[i]=read(); over(i,1,m) b[read()]++;
	f[0][0][0][0]=a[1];
	over(i,0,b[1]) over(j,0,b[2]) over(k,0,b[3]) over(l,0,b[4]) 
	{
		int x=1+i*1+j*2+k*3+l*4;
		if(i) f[i][j][k][l]=max(f[i][j][k][l],f[i-1][j][k][l]+a[x]);
		if(j) f[i][j][k][l]=max(f[i][j][k][l],f[i][j-1][k][l]+a[x]);
		if(k) f[i][j][k][l]=max(f[i][j][k][l],f[i][j][k-1][l]+a[x]);
		if(l) f[i][j][k][l]=max(f[i][j][k][l],f[i][j][k][l-1]+a[x]);
	}
	printf("%d\n",f[b[1]][b[2]][b[3]][b[4]]);
	return 0;
}

T6:

P1064 金明的预算方案

这个题,就是有依赖性背包的代表,可能也是因为有了这个题才有了有依赖性背包(一顿口糊,,)就是考虑只放主件,放主件和附件一,放主件和附件二,放主件和两个附件这四种情况就行,剩下的就是01包问题了,,
f [ i ] : f[i]: f[i]:表示在体积为i时的最优答案
状态转移方程:(太多了,就看代码吧)
答案: a n s = f [ V ] ans=f[V] ans=f[V]

代码:

#include<bits/stdc++.h>
#define int long long
#define D double
#define mp(s,t) make_pair(s,t)
#define pii pair<int,int>
#define fir first
#define sec second
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int ocean=0x7fffffff;
const int mod=1e9+7;
const int sea=1e6+7;
const int pool=1100;
struct hit{int w,v;}a[sea];//表示主件的重要度和费用 
struct bit{int w,v;}b[sea][3];//表示附件[依附在第几个主件][这个附件是这个主件的第几个附件]的重要度和费用 
int n,V,f[sea]; 
signed main()
{
	V=read(); n=read();
	over(i,1,n)
	{	
		int x=read(),y=read(),z=read();
		if(!z)	a[i].w=x,a[i].v=y*x;
		else b[z][0].w++,b[z][b[z][0].w].w=x,b[z][b[z][0].w].v=y*x;
	}
	over(i,1,n) for(int j=V;j>=a[i].w&&a[i].w;j--)
	{
		f[j]=max(f[j],f[j-a[i].w]+a[i].v);
		if(j>=a[i].w+b[i][1].w)
		f[j]=max(f[j],f[j-a[i].w-b[i][1].w]+a[i].v+b[i][1].v);
		if(j>=a[i].w+b[i][2].w)
		f[j]=max(f[j],f[j-a[i].w-b[i][2].w]+a[i].v+b[i][2].v);
		if(j>=a[i].w+b[i][1].w+b[i][2].w)
		f[j]=max(f[j],f[j-a[i].w-b[i][1].w-b[i][2].w]+a[i].v+b[i][1].v+b[i][2].v);
	}
	printf("%lld\n",f[V]);
	return 0;
}

T7:

P1063 能量项链

这道题,刚开始就没看懂题,翻了好久的题解才发现,上面有说珠子可以不按照顺序的,我才恍然大悟,这个是很模板的区间DP,还是要好练DP各种题型。
f [ i ] [ j ] : f[i][j]: f[i][j]:表示从i到j的最大能量值
状态转移方程式:
f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i ] [ k ] + f [ k ] [ j ] + a [ i ] ∗ a [ j ] ∗ a [ k ] ) f[i][j]=max(f[i][j],f[i][k]+f[k][j]+a[i]*a[j]*a[k]) f[i][j]=max(f[i][j],f[i][k]+f[k][j]+a[i]a[j]a[k])
答案: a n s = m a x ( f [ i ] [ n + i ] ) ans=max(f[i][n+i]) ans=max(f[i][n+i])

代码:

#include<bits/stdc++.h>
//#define int long long
#define D double
#define mp(s,t) make_pair(s,t)
#define pii pair<int,int>
#define fir first
#define sec second
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int ocean=0x7fffffff;
const int mod=1e9+7;
const int sea=1e6+7;
const int pool=1100;
int n,ans,a[sea],f[pool][pool];//fw[i][j]表示从i到j的最大能量值 
int main()
{
	n=read(); over(i,1,n) a[i+n]=a[i]=read();
	over(len,2,n+1) //枚举区间长度 
	{
		for(int i=1;i+len-1<=n*2;i++)//枚举左端点 
		{
			int j=i+len-1;//右端点 
			over(k,i+1,i+len-2)//枚举中间的情况 
			f[i][j]=max(f[i][j],f[i][k]+f[k][j]+a[i]*a[j]*a[k]);
		}
	}
	ans=0; 
	over(i,1,n) ans=max(ans,f[i][n+i]);
	printf("%d\n",ans);
	return 0;
}

T8:

P1156 垃圾陷阱

这个题,确实刚开始就想了好长时间,想到了背包的写法,但是不知道怎么处理时间,因为时间是要加要减的,然后就很迷了,,翻了翻题解,才发现这个写法,刚开始想的就是一维的,通过这道题我知道了还能记录时间点,这样去累计,而不是加时间段。
f [ i ] : f[i]: f[i]:表示高度为i的时候生存的最长时间
状态转移方程:
f [ j ] = m a x ( f [ j ] + a [ i ] . F ) f[j]=max(f[j]+a[i].F) f[j]=max(f[j]+a[i].F)//不吃
f [ j + a [ i ] . H ] = m a x ( f [ j ] + a [ i ] . T ) f[j+a[i].H]=max(f[j]+a[i].T) f[j+a[i].H]=max(f[j]+a[i].T)//吃
答案: f [ 0 ] f[0] f[0]

代码:

#include<bits/stdc++.h>
//#define int long long
#define D double
#define mp(s,t) make_pair(s,t)
#define pii pair<int,int>
#define fir first
#define sec second
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int ocean=0x7fffffff;
const int mod=1e9+7;
const int sea=1e6+7;
const int pool=1100;
struct hit{int T,F,H;}a[sea];
int V,n,f[sea];//表示高度为i的时候生存的最长时间是f[i] 
bool cmp(hit x,hit y){return x.T<y.T;}
int main()
{
	V=read(); n=read();
	over(i,1,n) a[i].T=read(),a[i].F=read(),a[i].H=read();
	sort(a+1,a+n+1,cmp);  memset(f,-1,sizeof f);f[0]=10;
	over(i,1,n) lver(j,V,0)
	{
		if(f[j]>=a[i].T)
		{
			if(j+a[i].H>=V) {printf("%d\n",a[i].T);return 0;}
			f[j+a[i].H]=max(f[j],f[j+a[i].H]);//不吃 
			f[j]=max(f[j],f[j]+a[i].F);//吃 
		}
	}
	printf("%d\n",f[0]);
	return 0;
}

T9:

P1026 统计单词个数

这个题,做的真的是艰辛啊,,,早就想写这个题了,但是对于字符串的DP一直是死掉的,见光死的那种,然后就在chdy大佬的代码帮助下,我真的,好好的写了一遍,自己写的!然后就过了,,开心死了,,所以这次要好好地琢磨一遍,然后好好写题解!
先说一下DP的部分吧:
还是跟下面一题的思想一样(我先写的过河再写的这个),先考虑设状态,还是,答案求什么设什么,然后在转移的时候要考虑无后效性,用之前的情况推出来后面的情况,然后就很好的写出来转移方程式了,配合上下面的图进行更好的理解。
然后就说一下那个 d e v o t devot devot的处理,因为是对于一段区间的字符串处理,然后对于每个位置进行选择,所以对于每一个单词串都要遍历一遍文章串,判断这个单词串是否可以做这个文章串的第k个位置的合法串,这里其实是可以用kmp先处理出来文章串和这个单词串之间的关系的,这样是有优化的,但是这个题的数据比较小就用不上了。
(下面画图对DP方面进行具体的解释了,,)
在这里插入图片描述
f [ i ] [ j ] : f[i][j]: f[i][j]:表示前i个字母分为j段时的最大贡献
状态转移方程:
f [ i ] [ j ] = m a x ( f [ k ] [ j − 1 ] + d e v o t ( k + 1 , i ) ) f[i][j]=max(f[k][j-1]+devot(k+1,i)) f[i][j]=max(f[k][j1]+devot(k+1,i))
答案: a n s = f [ c n t ] [ k k ] ans=f[cnt][kk] ans=f[cnt][kk]

代码:

#include<bits/stdc++.h>
//#define int long long
#define D double
#define mp(s,t) make_pair(s,t)
#define pii pair<int,int>
#define fir first
#define sec second
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int ocean=0x7fffffff;
const int mod=1e9+7;
const int sea=210;
const int pool=25;
const int stream=7;
int n,m,kk,cnt,v[sea],f[sea][50],w[sea];//f[i][j]表示前i个字母分为j段时的最大贡献是f[i][j],w[i]表示第i个文章串的长度为w[i] 
char ch[pool],a[sea],b[stream][pool],c[sea];
//ch[i]表示输入时候的文章串,a[i]表示整个文章串,b[i][j]表示第i个单词串的第j位,c[i]表示第i个单词串 
int devot(int x,int y)
{
	int s=0; memset(v,0,sizeof v);
	over(j,1,m)
	{
		over(i,x,y)
		{
			int flag=0;
			if(v[i]) continue;
			if(y-i+1<w[j]) break;
			over(k,1,w[j]) if(b[j][k]!=a[i+k-1]) {flag=1;break;}
			if(flag==0) v[i]=1,s++;
		}
	}
	return s;
}
int main()
{
	n=read(); kk=read()-1;
	over(i,1,n)
	{
		scanf("%s",ch+1); int x=strlen(ch+1);
		over(j,1,x) a[++cnt]=ch[j];
	}  
	m=read();
	over(i,1,m) scanf("%s",b[i]+1),w[i]=strlen(b[i]+1);
	over(i,1,cnt)//枚举整个串 
	over(j,0,kk)//枚举断点
	{
		if(j>i) break;
		over(k,j,i-1)//枚举上一个断点的位置 
		f[i][j]=max(f[i][j],f[k][j-1]+devot(k+1,i));
	} 
	printf("%d\n",f[cnt][kk]);
	return 0;
}

T10:

P1052 过河

这个题,完全可以从代码中看出我确实是认认真真地想了一个错解,但是到现在还觉得自己的思路是没有错的,,,但是还是屈服于题解了,,这个题正解DP的路径压缩,,什么啊,根本就是用数学的思想巧妙地进行离散化而已,,,说明一下这里为什么是要 i − j i-j ij,这里可以才用背包的思想,因为i从后面减过来的方案肯定无后效性的,这也就是我之前的思想为什么是错误的,没有考虑到后效性的原因,所以DP题,都要正反地好好想想,尤其是反过来的!!
f [ i ] : f[i]: f[i]:表示在第i个坐标的时候踩的最少的石子数
状态转移方程:
f [ i ] = m i n ( f [ i − j ] ) ( i − j > = 0 ) f[i]=min(f[i-j])(i-j>=0) f[i]=min(f[ij])(ij>=0)
f [ i ] + = m v p [ i ] f[i]+=mvp[i] f[i]+=mvp[i]
答案: a n s = m a x i = y + a [ i ] − 1 a [ i ] f [ i ] ans=max_{i=y+a[i]-1}^{a[i]}f[i] ans=maxi=y+a[i]1a[i]f[i]

代码:

#include<bits/stdc++.h>
//#define int long long
#define D double
#define mp(s,t) make_pair(s,t)
#define pii pair<int,int>
#define fir first
#define sec second
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int ocean=0x7fffffff;
const int mod=1e9+7;
const int sea=1e7+7;
const int pool=1100;
//int n,m,x,y,cnt,a[sea],f[sea][sea][sea],w[sea];
//a[i]表示第i块石头的坐标是a[i],w[i]表示第i种的跳跃方式是跳w[i]米,
//f[i][j][k]表示,在第i个石头处的第j个跳跃方式距离上个石头最近的距离是k的最少需要踩到的石头数量是f[i][j][k] 
map<int,int>mvp; 
//可恶的是,,,正解是路径压缩,,我听都没听过,,,DP还有路径压缩的,,, 
int n,m,x,y,ans,f[sea],d[sea],a[sea];
int main()
{
	n=read(); x=read();y=read(); 
	m=read(); over(i,1,m) a[i]=read();
	sort(a+1,a+m+1); over(i,1,m) d[i]=(a[i]-a[i-1])%2520,a[i]=a[i-1]+d[i],mvp[a[i]]=1;
	over(i,0,a[m]+y) f[i]=m; f[0]=0; 
	over(i,1,a[m]+y) over(j,x,y)
	{
		if(i-j>=0) f[i]=min(f[i],f[i-j]);
		f[i]+=mvp[i]; 
	} 
	ans=m; 
	over(i,a[m],a[m]+y-1) ans=min(ans,f[i]); 
	printf("%d",ans); 
//	int k=0; memset(f,0x3f,sizeof f); over(i,1,cnt) f[0][i][0]=0;
//	over(i,1,m) over(j,1,cnt) 
//	{
//		if(mvp[a[j]+k]) f[i][j][k]=min(f[i][j][k],f[i-1][j][k]+1),k=0;
//		else f[i][j][k+w[j]]=f[i][j][k],k+=w[j];
//	} 
//	printf("%d\n",f[m][cnt][0]);
	return 0;
}

T11:

P1095 守望者的逃离

这个题,确实经过之前的一些难题之后我就能写出来这些比较简单的DP了,还是比较开心的,这个题要记住的是有一点,守望者的跑步是不能和使不使用魔法放在一起的,要分成两层去看,因为当你使用法术的时候是“投机取巧”的,你会发现休息就是为了投机取巧,,(这样说的明白,,,别误会)所以分开处理的话就可以达到法术使用的最大化,然后再进行对法术和跑步的最优比较就好。
f [ i ] : f[i]: f[i]:表示时间为i的时候到达的最远距离
状态转移方程:(啊,,,太长了就看代码吧~~~)
答案: a n s = f [ t ] / f i r s t ( f [ i ] > = s ) ans=f[t]/first(f[i]>=s) ans=f[t]/first(f[i]>=s)

代码:

#include<bits/stdc++.h>
//#define int long long
#define D double
#define mp(s,t) make_pair(s,t)
#define pii pair<int,int>
#define fir first
#define sec second
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int ocean=0x7fffffff;
const int mod=1e9+7;
const int sea=1e6+7;
const int pool=1100;
int m,s,t,ans,f[sea]; 
int main()
{
	m=read(); s=read(); t=read();
	int k=m;
	over(i,1,t)
	{
		if(k>=10) f[i]=f[i-1]+60,k-=10;//用魔法 
		else f[i]=f[i-1],k+=4;//休息
	} 
	over(i,1,t) f[i]=max(f[i],f[i-1]+17);//跑步
	if(f[t]<=s) puts("No"),printf("%d\n",f[t]);
	else
	{
		over(i,1,t) if(f[i]>=s){ ans=i;break;} 
		puts("Yes"),printf("%d\n",ans);
	}
	return 0;
}

T12:

P1004 方格取数

这个题,确实,我有点偷懒了,直接写的四维的做法,等今天早上写完了吧,写完了我就去想想二维的做法。
f [ i ] [ j ] [ k ] [ l ] : f[i][j][k][l]: f[i][j][k][l]:表示第一个人到 ( i , j ) (i,j) (i,j),第二个人到 ( k , l ) (k,l) (k,l)时的最大答案
状态转移方程:
f [ i ] [ j ] [ k ] [ l ] = m a x ( f [ i − 1 ] [ j ] [ k ] [ l − 1 ] , m a x ( f [ i ] [ j − 1 ] [ k − 1 ] [ l ] , m a x ( f [ i − 1 ] [ j ] [ k − 1 ] [ l ] , f [ i ] [ j − 1 ] [ k ] [ l − 1 ] ) ) ) + a [ i ] [ j ] + a [ k ] [ l ] f[i][j][k][l]=max(f[i-1][j][k][l-1],max(f[i][j-1][k-1][l],max(f[i-1][j][k-1][l],f[i][j-1][k][l-1])))+a[i][j]+a[k][l] f[i][j][k][l]=max(f[i1][j][k][l1],max(f[i][j1][k1][l],max(f[i1][j][k1][l],f[i][j1][k][l1])))+a[i][j]+a[k][l]
答案: a n s = f [ n ] [ n ] [ n ] [ n ] ans=f[n][n][n][n] ans=f[n][n][n][n]

代码:

#include<bits/stdc++.h>
//#define int long long
#define D double
#define mp(s,t) make_pair(s,t)
#define pii pair<int,int>
#define fir first
#define sec second
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int ocean=0x7fffffff;
const int mod=1e9+7;
const int sea=10;
const int pool=1100;
int n,f[sea][sea][sea][sea],a[sea][sea];
int main()
{
	n=read(); memset(a,0,sizeof a);
	while(1)
	{
		int x=read(),y=read(),z=read();
		if(!x&&!y&&!z) break;
		a[x][y]=z;
	}
	over(i,1,n)	over(j,1,n) over(k,1,n) over(l,1,n)
	{
		f[i][j][k][l]=max(f[i-1][j][k][l-1],max(f[i][j-1][k-1][l],max(f[i-1][j][k-1][l],f[i][j-1][k][l-1])))+a[i][j]+a[k][l];
		if(i==k&&j==l) f[i][j][k][l]-=a[i][j];
	}
	printf("%d\n",f[n][n][n][n]);
	return 0;
}

T13:

P1736 创意吃鱼法

这个题虽然我之前就做过类似的题目(就是上面的最大正方形),但是写这道题,我看出来了,我是有些急,还有就是看到简单题就不知道好好去思考了,直接上手就写,然后就是没有看到有两条对角线,然后就直接暴毙了,接着就是没有看到不仅要求让保证对角线还要保证这个正方形上除了对角线剩下都是0,所以就又一次暴毙了,还有就是写完了没有很检查,写反了循环都不知道,我的天,我是怎么了,,,赶快赶快清醒过来别再胡写了,认真做好每一个小题才是必要的!
f 1 [ i ] [ j ] : f1[i][j]: f1[i][j]:表示以 ( i , j ) (i,j) (i,j)为右下角的时候的最大答案
f 2 [ i ] [ j ] : f2[i][j]: f2[i][j]:表示以 ( i , j ) (i,j) (i,j)为左下角的时候的最大答案
状态转移方程:(这个是真的不好写过来,,,看代码吧,,,)
答案: a n s = m a x ( f 1 [ i ] [ j ] , f 2 [ i ] [ j ] ) ans=max(f1[i][j],f2[i][j]) ans=max(f1[i][j],f2[i][j])

代码:

#include<bits/stdc++.h>
//#define int long long
#define D double
#define mp(s,t) make_pair(s,t)
#define pii pair<int,int>
#define fir first
#define sec second
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int ocean=0x7fffffff;
const int mod=1e9+7;
const int sea=2520;
const int pool=1100;
int n,m,ans,a[sea][sea],f1[sea][sea],f2[sea][sea],s1[sea][sea],s2[sea][sea]; //s1是纵着的0,s2是横着的0
int main()
{
	n=read(); m=read();
	over(i,1,n) over(j,1,m) a[i][j]=read();
	over(i,1,n) over(j,1,m) if(!a[i][j]) s1[i][j]=s1[i-1][j]+1,s2[i][j]=s2[i][j-1]+1;
	over(i,1,n) over(j,1,m) if(a[i][j]) f1[i][j]=min(s1[i-1][j],min(s2[i][j-1],f1[i-1][j-1]))+1;
	memset(s1,0,sizeof s1); memset(s2,0,sizeof s2);
	over(i,1,n) lver(j,m,1) if(!a[i][j]) s1[i][j]=s1[i-1][j]+1,s2[i][j]=s2[i][j+1]+1;
	over(i,1,n) lver(j,m,1) if(a[i][j]) f2[i][j]=min(s1[i-1][j],min(s2[i][j+1],f2[i-1][j+1]))+1;
	over(i,1,n) over(j,1,m) ans=max(ans,max(f1[i][j],f2[i][j])); 
	printf("%d\n",ans);
	return 0;
}

T14:

P1108 低价购买
双倍经验:P2687 [USACO4.3]逢低吸纳Buy Low, Buy Lower(高精版,可用long double 水过)
这个题,确实就是LIS和LIS记录方案数,然后就看那个去重,想了好半天,才明白是什么意思。
f [ i ] : f[i]: f[i]:表示到i的最长上升子序列的长度
g [ i ] : g[i]: g[i]:表示到i的最长上升子序列的方案数
状态转移方程:
f [ i ] = m a x ( f [ j ] + 1 ) f[i]=max(f[j]+1) f[i]=max(f[j]+1)
g [ i ] g[i] g[i]就看代码吧,,,)
答案: a n s 1 = m a x ( f [ i ] ) / a n s 2 + = g [ i ] ( f [ i ] = = a n s 1 ) ans1=max(f[i])/ans2+=g[i](f[i]==ans1) ans1=max(f[i])/ans2+=g[i](f[i]==ans1)

代码:

#include<bits/stdc++.h>
#define int long long
#define D long double
#define mp(s,t) make_pair(s,t)
#define pii pair<int,int>
#define fir first
#define sec second
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int ocean=0x7fffffff;
const int mod=1e9+7;
const int sea=5050;
const int pool=1100;
int n,a[sea]; D ans1,ans2,f[sea],g[sea];
signed main()
{
	n=read(); over(i,1,n) a[i]=read(); over(i,1,n) f[i]=1;
	over(i,1,n)	over(j,1,i-1) if(a[i]<a[j]) f[i]=max(f[i],f[j]+1);
	over(i,1,n) ans1=max(ans1,f[i]);
	over(i,1,n) 
	{
		if(f[i]==1) g[i]=1;//这里是细节处理,考虑一下,当最长上升子序列是1时候,方案数就是1啊,对于下面的更新就是在这个基础上进行记录的。
		over(j,1,i-1)
		{
			if(f[i]==f[j]+1&&a[i]<a[j]) g[i]+=g[j];//对于合法的更新进行记录方案数
			else if(f[i]==f[j]&&a[i]==a[j]) g[i]=0;//这里是需要去重的
		}
		if(ans1==f[i]) ans2+=g[i];
	}
	printf("%.0LF %.0LF\n",ans1,ans2);
	return 0;
}

T15:

P3147 [USACO16OPEN]262144
这个题,算是一个区间DP仿倍增吧,,(其实就是倍增的简化),首先我们看到这个题之后就是要求在这个数字之后可以合并的区间长度,所以就想到了再写倍增的时候就会有的转移方程式。
f [ i ] [ l ] = r : f[i][l]=r: f[i][l]=r:表示从l到r区间可以合成i
状态转移方程:
f [ i ] [ j ] = f [ i − 1 ] [ f [ i − 1 ] [ j ] ] ( f [ i ] [ j ] > 0 ) f[i][j]=f[i-1][f[i-1][j]](f[i][j]>0) f[i][j]=f[i1][f[i1][j]](f[i][j]>0)
a n s = f [ i ] [ j ] ans=f[i][j] ans=f[i][j]

代码:

#include<bits/stdc++.h>
//#define int long long
#define D double
#define mp(s,t) make_pair(s,t)
#define pii pair<int,int>
#define fir first
#define sec second
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int ocean=0x7fffffff;
const int mod=1e9+7;
const int pool=1e6+7;
const int sea=1100;
int n,ans,f[61][362144];
int main()
{
	n=read(); over(i,1,n) f[read()][i]=i+1;
	over(i,2,58) over(j,1,n)
	{ 
		if(!f[i][j]) f[i][j]=f[i-1][f[i-1][j]]; 
		if(f[i][j]) ans=i;
	}
	printf("%d\n",ans);
	return 0;
}
//这里说一下为什么是58,是因为最大是2的18次方,所以就在40上再加上18即可。

T16:

P1220 关路灯
这个题就是区间DP的基础题,就是在不断的枚举区间去比较出最优答案,区间DP的最常见的 f f f数组就是表示区间 [ l , r ] [l,r] [l,r]的贡献,可以用递归来写,也可以for循环枚举。
关于这个题,由于是要关完所有的灯,而且是每次可以关左边,也可以关右边,然后考虑这个是怎么转化出来的很明显是从中间原来老张的位置开始向两边处理,不断比较两遍的所需要的功耗。

f [ i ] [ j ] [ 1 / 0 ] : f[i][j][1/0]: f[i][j][1/0]:表示从i到j关掉左边(右边)的最优的最少功耗
状态转移方程:
f [ i ] [ j ] [ 0 ] = m i n ( f [ i + 1 ] [ j ] [ 0 ] + s u m ∗ , P f [ i + 1 ] [ j ] [ 1 ] + s u m ∗ P ) ; f [ i ] [ j ] [ 1 ] = m i n ( f [ i ] [ j − 1 ] [ 0 ] + s u m ∗ P , f [ i ] [ j − 1 ] [ 1 ] + s u m ∗ P ) ; f[i][j][0]=min(f[i+1][j][0]+sum*,Pf[i+1][j][1]+sum*P);\\ f[i][j][1]=min(f[i][j-1][0]+sum*P,f[i][j-1][1]+sum*P); f[i][j][0]=min(f[i+1][j][0]+sum,Pf[i+1][j][1]+sumP);f[i][j][1]=min(f[i][j1][0]+sumP,f[i][j1][1]+sumP);
答案: m i n ( f [ 1 ] [ n ] [ 1 ] , f [ 1 ] [ n ] [ 0 ] ) min(f[1][n][1],f[1][n][0]) min(f[1][n][1],f[1][n][0])

代码:

#include<bits/stdc++.h>
//#define int long long
#define D double
#define mp(s,t) make_pair(s,t)
#define pii pair<int,int>
#define fir first
#define sec second
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int ocean=0x7fffffff;
const int mod=1e9+7; 
const int sea=1100;
const int pool=1100;
int n,s,a[sea],d[sea],v[sea],sum[sea][sea],f[sea][sea][2];
int main()
{
	n=read();  s=read(); over(i,1,n) d[i]=read(),v[i]=read(),v[i]+=v[i-1];
	memset(f,0x3f,sizeof f); f[s][s][1]=f[s][s][0]=0;
	over(j,s,n) lver(i,j-1,1) 
	{
		f[i][j][0]=min(f[i+1][j][0]+(v[n]-(v[j]-v[i]))*(d[i+1]-d[i]),f[i+1][j][1]+(v[n]-(v[j]-v[i]))*(d[j]-d[i]));
        f[i][j][1]=min(f[i][j-1][0]+(v[n]-(v[j-1]-v[i-1]))*(d[j]-d[i]),f[i][j-1][1]+(v[n]-(v[j-1]-v[i-1]))*(d[j]-d[j-1]));	
	}//原来想把(v[n]-(v[j]-v[i]))预处理一下的,但是不知道为什么这样就错了,,,
	printf("%d\n",min(f[1][n][0],f[1][n][1]));
	return 0;
}


T17:

P4170 [CQOI2007]涂色
这个题,也是区间DP的题目,所以就考虑从那边开始,这个题我刚开始还以为是从两边向中间转移,就一直好想觉得有后效性,其实如果从中间向两边推就没有后效性了。

代码:

#include<bits/stdc++.h>
//#define int long long
#define D double
#define mp(s,t) make_pair(s,t)
#define pii pair<int,int>
#define fir first
#define sec second
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int ocean=0x7fffffff;
const int mod=1e9+7;
const int sea=1e3+7;
const int pool=1100;
char ch[sea];
int n,f[sea][sea];
int main()
{
	scanf("%s",ch+1); n=strlen(ch+1); memset(f,0x3f,sizeof f);
	over(i,1,n) f[i][i]=1;
	over(len,1,n-1) for(int i=1,j=1+len;j<=n;i++,j++)
	{
		if(ch[i]==ch[j]) f[i][j]=min(f[i+1][j],f[i][j-1]);
		else over(k,i,j-1) f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);
	}
	printf("%d\n",f[1][n]);
	return 0;
}

T18:

P4302 [SCOI2003]字符串折叠
这个题,重要的是在于找到这个正确的转移方程,毕竟DP也有点向构造题,这就很不好想,再说了,DP套上字符串,就是会有些不好写,但是大部分是数据范围较小的暴力枚举的题目,所以就是在考你DP的能力的,所以写DP,尤其是区间DP,真的要写一下字符串DP,这才有意思,反正我是被字符串DP差点搞垮了,,,
先说一下这个题的正解吧,上眼看上去这是个比较不好写的大模拟+DP,其实就是,只不过是我的字符串的题写的少而已,所以就比较不好去进行处理,一个很是暴力的思想,就是:枚举所有有用区间然后判断即可,至于DP就是求了一下最小值而已。
可以从下面的图进行理解:
在这里插入图片描述
不断枚举 l e n len len l l l,进行check就好,对了,这样你能怀疑这是 O ( n 4 ) O(n^4) O(n4)的,但是你可以看一下,我看到 l l l不是 l e n len len的倍数的时候我就直接 c o n t i n u e continue continue 就会卡掉好多,这个复杂度是 l o g log log的,然后就可以过了。
f [ i ] [ j ] [ 1 ] : f[i][j][1]: f[i][j][1]:表示l到r区间内的最小重叠子串长度
状态转移方程式:(看代码吧)
答案: a n s = f [ 1 ] [ n ] ans=f[1][n] ans=f[1][n]

代码:

#include<bits/stdc++.h>
//#define int long long
#define D double
#define mp(s,t) make_pair(s,t)
#define pii pair<int,int>
#define fir first
#define sec second
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int ocean=0x7fffffff;
const int mod=1e9+7;
const int sea=110;
const int pool=1100;
char s[sea];
int n,a[sea],f[sea][sea];//表示l到r区间内的最小重叠子串长度 
bool check(char s[],int x,int y){over(i,y,x-1) if(s[i]!=s[i%y]) return 0; return 1;} 
int main()
{
	scanf("%s",s+1); n=strlen(s+1);
	over(i,1,9) a[i]=1; over(i,10,99) a[i]=2;a[100]=3;
	memset(f,0x3f,sizeof f); over(i,1,n) f[i][i]=1;  
	over(len,2,n) for(int i=1,j=i+len-1;j<=n;i++,j++) 
	{
		over(k,i,j-1) f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);//不用合并的
		over(k,i,j-1) 
		{
			int l=k-i+1;//枚举子序列的子序列 
			if(len%l!=0) continue;
			if(check(s+i,len,l))		
			f[i][j]=min(f[i][j],f[i][k]+2+a[len/l]);
		}
	}
	printf("%d\n",f[1][n]);
	return 0;
}

T19:

P2470 [SCOI2007]压缩

这个题,和上道题很像,就是在字符串处理的规则上有些不同,这个是两个相同字符串可以重叠记录(这点就是为什么找到之后对于两个长度进行整除的奇偶性判断,以及下面对于之后的更新是 ( i + j ) / 2 (i+j)/2 (i+j)/2),而不是记录个数,还是看个图,我们现在设的状态是 f [ i ] [ j ] [ 0 / 1 ] f[i][j][0/1] f[i][j][0/1]表示在 [ i , j ] [i,j] [i,j]的区间之间有没有 m m m这个标记,然后在转移的时候就可以参考下面的图,处理 [ i , j ] [i,j] [i,j]之间的最小重叠的的字符,但是由于是对于 [ i , j ] [i,j] [i,j]之间的处理,所以可以脑补一下, i i i的前面一定会有个 M M M 。下面的图就是对于可以在 [ i , j ] [i,j] [i,j]中放 M M M的转移。

在这里插入图片描述
f [ i ] [ j ] [ 0 / 1 ] : f[i][j][0/1]: f[i][j][0/1]:表示默认的最前面是有一个M,1是可以在中间放M,0是M不放在中间的最小重复的字符串长度。
状态转移方程:(跟上面的那道题几乎一样)
答案: a n s = m i n ( f [ 1 ] [ n ] [ 1 ] , f [ 1 ] [ n ] [ 0 ] ) ans=min(f[1][n][1],f[1][n][0]) ans=min(f[1][n][1],f[1][n][0])

代码:

#include<bits/stdc++.h>
//#define int long long
#define D double
#define mp(s,t) make_pair(s,t)
#define pii pair<int,int>
#define fir first
#define sec second
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int ocean=0x7fffffff;
const int mod=1e9+7;
const int sea=60;
const int pool=1100;
char s[sea];
int n,f[sea][sea][2]; //f[i][j][1/0]是默认的最前面是有一个M的,1是可以在中间放M,0是M不放在中间 。
bool check(int x,int y)
{
	int len=y-x+1; if(len&1) return 0;
	int mid=len/2;
	over(i,x,y-mid) if(s[i]!=s[i+mid]) return 0;
	return 1;
}
int main()
{
	scanf("%s",s+1); n=strlen(s+1);
	memset(f,0x3f,sizeof(f)); over(i,1,n) f[i][i][1]=f[i][i][0]=1;
	over(len,2,n) for(int i=1,j=len+i-1;j<=n;j++,i++)
	{
		f[i][j][0]=f[i][j][1]=len;
		over(k,i,j-1) f[i][j][1]=min(f[i][j][1],min(f[i][k][1],f[i][k][0])+min(f[k+1][j][1],f[k+1][j][0])+1);  //(这点就是上面的图所表示的)在k之前可以加M也可以不加M,之后也是可以加可以不加,然后去两次min然后再加上1就好了。这里就是处理的[i,j]可以放M的最小的重复字符串
		
		over(k,i,j-1) f[i][j][0]=min(f[i][j][0],f[i][k][0]+j-k);
		if(check(i,j)) f[i][j][0]=min(f[i][j][0],f[i][(i+j)/2][0]+1);
	}
	
	printf("%d\n",min(f[1][n][0],f[1][n][1]));
	return 0;
}

T20:

P1352 没有上司的舞会
终于进入树形DP了,尽管之前我是特别的写过树形DP的一些题目,但是我还是要一样一样捡起来啊。
这个题是我写树形DP的第一道题,从新捡回来吧。
f [ i ] [ 0 / 1 ] : f[i][0/1]: f[i][0/1]:表示 i i i这个点选于不选的最大快乐值
状态转移方程:
f [ x ] [ 1 ] = ∑ f [ y ] [ 0 ] f[x][1]=\sum{f[y][0]} f[x][1]=f[y][0]
f [ x ] [ 0 ] = ∑ m a x ( f [ y ] [ 1 ] , f [ y ] [ 0 ] ) f[x][0]=\sum{max(f[y][1],f[y][0])} f[x][0]=max(f[y][1],f[y][0])
答案: a n s = m a x ( f [ x ] [ 0 ] , f [ x ] [ 1 ] ) ans=max(f[x][0],f[x][1]) ans=max(f[x][0],f[x][1])

代码:

#include<bits/stdc++.h>
//#define int long long
#define D double
#define mp(s,t) make_pair(s,t)
#define pii pair<int,int>
#define fir first
#define sec second
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int ocean=0x7fffffff;
const int mod=1e9+7;
const int sea=1e6+7;
const int pool=1100;
struct see{int next,ver,edge,x;}e[sea<<1];
int n,tot,ans,root,last[sea],fa[sea],f[sea][2];
void add(int x,int y){e[++tot].ver=y;e[tot].next=last[x];last[x]=tot;}
void dfs(int x)
{
	for(int i=last[x];i;i=e[i].next)
	{
		int y=e[i].ver; //if(fa==y) continue;
		dfs(y);
		f[x][0]+=max(f[y][0],f[y][1]);
		f[x][1]+=f[y][0];
	}
}
int main()
{
	n=read(); over(i,1,n) f[i][1]=read();
	while(1)
	{
		int x=read(),y=read();
		if(!x&&!y) break; add(y,x);fa[x]=y;
	} 
	over(i,1,n) if(!fa[i]) {root=i;break;}
	dfs(root);
	ans=max(f[root][1],f[root][0]); printf("%d\n",ans);
	return 0;
}

T21:

P1122 最大子树和

这个题就是上面的翻版,这里是要求一个点下面的节点和,然后就是去个max,注意负数。
f [ x ] : f[x]: f[x]:表示x节点的最大子树和。
状态转移方程:
f [ x ] = ∑ m a x ( 0 , f [ y ] ) f[x]=\sum{max(0,f[y])} f[x]=max(0,f[y])
答案: a n s = m a x ( f [ x ] ) ans=max(f[x]) ans=max(f[x])

代码:

#include<bits/stdc++.h>
//#define int long long
#define D double
#define mp(s,t) make_pair(s,t)
#define pii pair<int,int>
#define fir first
#define sec second
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int ocean=0x7fffffff;
const int mod=1e9+7;
const int sea=1e6+7;
const int pool=1100;
struct see{int next,ver,edge,x;}e[sea<<1];
int n,tot,ans,last[sea],f[sea];
void add(int x,int y){e[++tot].ver=y;e[tot].x=x;e[tot].next=last[x];last[x]=tot;}
void dfs(int x,int fa)
{
	for(int i=last[x];i;i=e[i].next)
	{
		int y=e[i].ver; if(y==fa) continue;
		dfs(y,x);f[x]+=max(0,f[y]);
	}
}
signed main()
{
	n=read(); over(i,1,n) f[i]=read();
	over(i,1,n-1)
	{
		int x=read(),y=read();
		add(x,y);add(y,x);
	} 
	dfs(1,0);
	over(i,1,n) ans=max(ans,f[i]); printf("%d\n",ans);
	return 0;
}

T22:

P4342 [IOI1998]Polygon

这个题,做完真的很开心,因为是自己全部弄明白之后自己敲的,然后一遍过,这就很难得了,,,这是我见到的第三个环形DP的题了,需要总结一下环形DP的技巧,就是破环成链,最重要的,这样可以给你少一重循环,然后复杂度就从 O ( n 4 ) O(n^4) O(n4)变到了 O ( n 3 ) O(n^3) O(n3)。关于这个题呢,我之前就看上了,但是当时对DP没有什么印象,就没怎么写,今天写觉得好开心,是区间DP的逻辑大题,还是比较值得一做的。
f [ i ] [ j ] [ 1 ] : f[i][j][1]: f[i][j][1]:表示最大值
f [ i ] [ j ] [ 0 ] : f[i][j][0]: f[i][j][0]:表示最小值
状态转移方程:(这个其实就是自己脑补一下的事情)
a n s : m a x ( f [ i ] [ i = n − 1 ] ) ans:max(f[i][i=n-1]) ans:max(f[i][i=n1])

代码:

#include<bits/stdc++.h>
//#define int long long
#define D double
#define mp(s,t) make_pair(s,t)
#define pii pair<int,int>
#define fir first
#define sec second
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int ocean=0x7fffffff;
const int mod=1e9+7;
const int sea=1e6+7;
const int pool=1100;
int n,a[sea],op[sea],re[pool][pool];
int f[pool][pool][2],ans=-ocean;//表示区间[i,j]的最大(最小)答案是多少 
int main()
{
	n=read(); 
	over(i,1,n)
	{
		char ch; cin>>ch; 
		if(ch=='t') op[i+n]=op[i]=1; else op[i+n]=op[i]=2;
		a[i]=read(); a[i+n]=a[i];
	}
	over(i,1,2*n) over(j,1,2*n) //破环成链
	if(i==j) f[i][j][0]=f[i][j][1]=a[i];
	else f[i][j][0]=-ocean,f[i][j][1]=ocean;  
	over(len,1,2*n-1) for(int i=1,j=len+i;j<=n*2;i++,j++) over(k,i,j-1)
	{
		//加 
		if(op[k+1]==1)
		{ 
			f[i][j][0]=max(f[i][j][0],f[i][k][0]+f[k+1][j][0]);
			f[i][j][1]=min(f[i][j][1],f[i][k][1]+f[k+1][j][1]);
		}
		//乘 
		else 
		{
			f[i][j][0]=max(f[i][j][0],f[i][k][0]*f[k+1][j][0]);
			f[i][j][0]=max(f[i][j][0],f[i][k][1]*f[k+1][j][1]);
			f[i][j][1]=min(f[i][j][1],f[i][k][1]*f[k+1][j][0]);
			f[i][j][1]=min(f[i][j][1],f[i][k][0]*f[k+1][j][1]);
			f[i][j][1]=min(f[i][j][1],f[i][k][1]*f[k+1][j][1]);
			f[i][j][1]=min(f[i][j][1],f[i][k][0]*f[k+1][j][0]);
		}
	}
	over(i,1,n) ans=max(ans,f[i][i+n-1][0]); printf("%d\n",ans);
	over(i,1,n) if(f[i][i+n-1][0]==ans) printf("%d ",i);
	return 0;
}

T23:

P3205 [HNOI2010]合唱队

这个题,其实和关路灯很像,就是在不停的左右找答案直接写解法吧。
f [ i ] [ j ] [ 1 ] : f[i][j][1]: f[i][j][1]:表示区间 [ i , j ] [i,j] [i,j]的最后放的 j j j在最右边
f [ i ] [ j ] [ 0 ] : f[i][j][0]: f[i][j][0]:表示区间 [ i , j ] [i,j] [i,j]的最后放的 i i i在最左边
状态转移方程:
f [ i ] [ j ] [ 0 ] = ( f [ i + 1 ] [ j ] [ 0 ] ∗ ( a [ i ] < a [ i + 1 ] ) + f [ i + 1 ] [ j ] [ 1 ] ∗ ( a [ j ] > a [ i ] ) ) f [ i ] [ j ] [ 1 ] = ( f [ i ] [ j − 1 ] [ 1 ] ∗ ( a [ j − 1 ] < a [ j ] ) + f [ i ] [ j − 1 ] [ 0 ] ∗ ( a [ j ] > a [ i ] ) ) f[i][j][0]=(f[i+1][j][0]*(a[i]<a[i+1])+f[i+1][j][1]*(a[j]>a[i]))%mod; \\f[i][j][1]=(f[i][j-1][1]*(a[j-1]<a[j])+f[i][j-1][0]*(a[j]>a[i]))%mod; f[i][j][0]=(f[i+1][j][0](a[i]<a[i+1])+f[i+1][j][1](a[j]>a[i]))f[i][j][1]=(f[i][j1][1](a[j1]<a[j])+f[i][j1][0](a[j]>a[i]))
答案: a n s = f [ 1 ] [ n ] [ 1 ] + f [ 1 ] [ n ] [ 0 ] ans=f[1][n][1]+f[1][n][0] ans=f[1][n][1]+f[1][n][0]

代码:

#include<bits/stdc++.h>
#define int long long
#define D double
#define mp(s,t) make_pair(s,t)
#define pii pair<int,int>
#define fir first
#define sec second
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int ocean=0x7fffffff;
const int mod=19650827;
const int sea=1e6+7;
const int pool=1100;
int n,a[sea],ans,f[pool][pool][2];
signed main()
{
	n=read(); over(i,1,n) a[i]=read();
	over(i,1,n) f[i][i][0]=1;
	over(len,1,n-1) for(int i=1,j=i+len;j<=n;i++,j++)
	{
		f[i][j][0]=(f[i+1][j][0]*(a[i]<a[i+1])+f[i+1][j][1]*(a[j]>a[i]))%mod;
		f[i][j][1]=(f[i][j-1][1]*(a[j-1]<a[j])+f[i][j-1][0]*(a[j]>a[i]))%mod;
	}
	printf("%lld\n",(f[1][n][1]+f[1][n][0])%mod);
	return 0;
}

T24:

P5615 [MtOI2019]时间跳跃

这应该算是在中间替补的一道题,,,原来的11月前写完的计划被我鸽了,,然后就写到了大概25道左右就鸽了,,,表示要开始复习模板和考试了,所以这些题可能会鸽一些,所以就很皮很皮的改成了三十题,,,这题也是在前两天打洛谷的时候写的,写的真的很辛苦,考场上就想了整整两个小时,好不容易一个小时就切掉了T1,T2,然后想T3,,,就想了好长时间,关键是当时大佬还给了提示,说是DP,背包,容斥,但是,,,一点用都没用,,我认为一道DP题你要是光知道它是DP,,,你是写不出来的,,,真的,,我尝试了,,没用,,想不到,但是真的有位大佬,在极端的十分钟就A掉了, 蒟蒻表示这辈子可能都达不到大佬的这种高度,包括下面的思路和代码,都是看着大佬的正写的,,,再次 % % % % % % % \%\%\%\%\%\%\% %%%%%%% 这位大佬:Froggy
好了,好好说下正解:
这个题首先你要知道凸多边形的构造方法: s u m < 2 m a x sum<2max sum<2max ,可以自己脑补一下,一个多边形的最长边如果不大于其余边的和的话就不能构成一个多边形,可以把其余的边拼成一条直线如果没有一个弧度就无法构成,这点参考三角形两边之和大于第三边的定理,,(这个其实很显然的,,,,)

然后就可以先推一样例,他是让你找到方案,并把方案中边的个数加来,我刚开始考虑的这可能是个数学题,就开始想推公式,结果大佬说是DP就开始想状态:设

f [ i ] [ j ] f[i][j] f[i][j] 表示前 i i i个数和为 j j j的权值和,权值就是这个集合中边的个数和
g [ i ] [ j ] g[i][j] g[i][j] 表示前 i i i 个数和为 j j j 的方案数

然后就来想一下怎么状态转移吧:
看一下 g [ i ] [ j ] g[i][j] g[i][j],这就是个背包自行转移吧。
关于 f [ i ] [ j ] f[i][j] f[i][j]要说一下怎么转移,在你加入 i i i的时候可以以一个背包的思想进行处理,在f[i-1][j]中加入一条长度是 i i i的边就把 f [ i ] [ j ] f[i][j] f[i][j]加上 g [ i ] [ j ] g[i][j] g[i][j]的上一层背包状态就好。(原因看下面)
g [ i ] [ j ] = g [ i ] [ j ] + g [ i − 1 ] [ j − i ] f [ i ] [ j ] = f [ i ] [ j ] + g [ i − 1 ] [ j − i ] + f [ i ] [ j − i ] g[i][j]=g[i][j]+g[i-1][j-i]\\ f[i][j]=f[i][j]+g[i-1][j-i]+f[i][j-i] g[i][j]=g[i][j]+g[i1][ji]f[i][j]=f[i][j]+g[i1][ji]+f[i][ji]

更新答案的时候就是 f [ i ] [ j ] + g [ i ] [ j ] f[i][j]+g[i][j] f[i][j]+g[i][j],考虑一下为什么在更新权值和和答案的时候要把 f [ i ] [ j ] f[i][j] f[i][j] 加上 g [ i ] [ j ] g[i][j] g[i][j] 呢,因为 f [ i ] [ j ] f[i][j] f[i][j]表示前i个数和为j的权值和,权值就是这个集合中边的个数和,所以在更它的时候是从他上个转移过来的,就是在这个时候,我们“确定好的边数”并没有算上最长边 i i i 。幸亏每个方案的权值是使用的木棍的个数,因此我们计算边 i i i 的贡献只需要再加上方案数即可。

注意一下这个写法其实是 O ( n 2 ∗ m a x s u m ) O(n^2*maxsum) O(n2maxsum) 的所以就好好观察一下就会找到一个更优的优化,首先是比较显然的滚动数组(其实这个滚动数组很没有必要可以省去),这样就吧复杂度压下去了,但是 m a x s u m maxsum maxsum是近乎于 n 2 n^2 n2 的,所以呢,再仔细观察一下,当发个最大边最大为 5000 5000 5000 ,形成凸多边形的性质是 s u m < 2 m a x sum<2max sum<2max ,所以其实在 5000 5000 5000 之前就可以停止向后扫了,之后的 s u m sum sum 是可以全部进行记录的,我在下面就记录到了 f [ i ] [ 5001 ] f[i][5001] f[i][5001] 里面。这样最后的复杂度就在 O ( n 2 ) O(n^2) O(n2)了。
(代码中的注释其实写的挺详细的)

还有一个更好更短的写法:容斥+背包(我都写背包了,,,竟然没有想到如容斥一下更快,,,我,,又一次傻掉了,,)

代码:

#include<bits/stdc++.h>
#define int long long
#define D double
#define mp(s,t) make_pair(s,t)
#define pii pair<int,int>
#define fir first
#define sec second
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int ocean=0x7fffffff;
const int mod=1000000007;
const int sea=5000;
const int pool=1100;
int T,n,f[2][sea+10],g[2][sea+10],ans[sea];
//f[i][j]表示前i个数和为j的权值和,权值就是这个集合中边的个数和
//g[i][j]表示前i个数和为j的方案数 
int ksm(int a,int b)
{
	int s=1;
	while(b)
	{
		if(b&1) s=s*a%mod;
		a=a*a%mod; b>>=1; 
	}
	return s%mod;
}
void ycl()
{
	g[0][0]=1;
	over(i,1,sea)
	{
		memcpy(f[i&1],f[(i-1)&1],sizeof f[i&1]);
		memcpy(g[i&1],g[(i-1)&1],sizeof g[i&1]);
		ans[i]=ans[i-1];
		//先赋成上一个的答案,然后对答案进行更新 
		over(j,i+1,sea+1) ans[i]=(ans[i]+f[(i-1)&1][j]+g[(i-1)&1][j])%mod;
		//说一下记录5001的原因,因为sum<2*max所以当sum大于5000的时候一定是可以记录的 
		//所以就不用再加到(n)(n-1)/2了  
		lver(j,sea+1,sea+1-i)
		{
			g[i&1][sea+1]+=g[(i-1)&1][j];//dp[i][5001]记录的是前i个数和>5000的方案数
			f[i&1][sea+1]+=f[(i-1)&1][j]+g[(i-1)&1][j];//f[i][5001]记录的是前i个数和>5000的权值和 
			g[i&1][sea+1]%=mod; f[i&1][sea+1]%=mod; 
		}
		lver(j,sea,0)
		{
			if(j>=i)
			{
				g[i&1][j]+=g[(i-1)&1][j-i];//对于方案数就是个背包的转移 
				f[i&1][j]+=f[(i-1)&1][j-i]+g[(i-1)&1][j-i];//对于权值和是要加上可放的权值和可放方案数的 
				//这里解释一下为什么记录总权值的时候要加上方案数,
				//是因为在每个边集中加入一条边的权值和就等价于在原本的权值和上加上等同于边集的个数的数
			}
			g[i&1][j]%=mod;f[i&1][j]%=mod;
		}
	}
}
signed main()
{
	ycl();T=read(); over(t,1,T) n=read(),printf("%lld\n",ans[n]*ksm(ksm(2,n),mod-2)%mod); 
	return 0;
}

T25:

P2216 [HAOI2007]理想的正方形

这个好像跟DP没有那么大的关系,但是不知道为什么就跑进了我的任务列表里面,可能是我写完最大子矩阵后推荐的题目吧,那就过来凑个数吧,,(皮)
这个题,上眼一看就是RMQ问题,所以,,ST表啊,然后就写了,,可能是由于是写上面的最大正方形,然后就想着以有下角开始写,但是那样写不知道为什么会错,chdy大佬当时给我改成左上角开始就过了,这样就很奇妙了,,,好吧这个题其实单调队列也能做,推荐大佬博客:单调队列的做法

代码:

#include<bits/stdc++.h>
#define int long long
#define D double
#define mp(s,t) make_pair(s,t)
#define pii pair<int,int>
#define fir first
#define sec second
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int ocean=0x7fffffff;
const int mod=1e9+7;
const int sea=1100;
const int pool=1100;
//struct see{int next,ver,edge,x;}e[sea<<1];
//int n,tot,ans,last[sea];
//void add(int x,int y,int z){e[++tot].ver=y;e[tot].x=x;e[tot].edge=z;e[tot].next=last[x];last[x]=tot;}
int n,m,k,ans=ocean,a[sea][sea],Log[sea],st1[sea][sea][3],st2[sea][sea][3];
void ycl()
{
	int tm=Log[k];
	over(i,1,n) over(j,1,m) st1[i][j][0]=st2[i][j][0]=a[i][j];
	over(h,1,n)
	{
		over(j,1,tm) over(i,1,m-(1<<j)+1)
		st1[h][i][j]=max(st1[h][i][j-1],st1[h][i+(1<<(j-1))][j-1]),
		st2[h][i][j]=min(st2[h][i][j-1],st2[h][i+(1<<(j-1))][j-1]);
	}
}
int ask1(int x,int y)
{
	int s=-ocean,kk=Log[k];
	over(i,x,x+k-1) s=max(s,max(st1[i][y][kk],st1[i][y+k-1-(1<<kk)+1][kk])); 
	return s;
}
int ask2(int x,int y)
{
	int s=ocean,kk=Log[k];
	over(i,x,x+k-1) s=min(s,min(st2[i][y][kk],st2[i][y+k-1-(1<<kk)+1][kk])); 
	return s;
}
signed main()
{
	n=read(); m=read(); k=read(); over(i,1,n) over(j,1,m) a[i][j]=read();
	over(i,1,max(n,m)) if(i!=1) Log[i]=Log[i>>1]+1 ;ycl(); 
	over(i,1,n-k+1) over(j,1,m-k+1) ans=min(ans,ask1(i,j)-ask2(i,j)); printf("%lld\n",ans);
	return 0;
}

T26:

P1131 [ZJOI2007]时态同步

代码:


T27:

P1273 有线电视网

代码:


T28:

P2279 [HNOI2003]消防局的设立

代码:


T29:

P2577 [ZJOI2005]午餐

这个题,确实我觉得像个背包构造,就是有很多背包让你去放物品,限制很多,不是很好想,刚开始跟本不知道怎么下手,翻了翻题解才知道怎么写,具体看注释吧写的挺详细的。

代码:

#include<bits/stdc++.h>
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i<=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-') w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0') s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int sea=210;	
int n,f[sea][sea*sea],ans=0x3fffffff,sum[sea];//f[i][j]表示第i,j第i个人在1餐厅打饭的总时间是j吃完饭的最早时刻 
struct hit{int x,y;}a[sea];
bool cmp(hit aa,hit bb) {if(aa.y==bb.y)return aa.x<bb.x;return aa.y>bb.y;}
int main()
{
	n=read();
	over(i,1,n) a[i].x=read(),a[i].y=read();
	sort(a+1,a+n+1,cmp);
	over(i,1,n) sum[i]=sum[i-1]+a[i].x;//sum[]表示打饭时间的总和,提前处理便于下面的转移式 
	memset(f,127,sizeof(f)); f[0][0]=0;
	//我认为这个题应该是背包的变形,可以用背包的是思想去理解 
	over(i,1,n) over(j,0,sum[i])//这里可以看做是背包,每个打饭的时间可以同步吃饭的时间是总共的打饭的时间 
	{
		//去一号餐厅 
		if(j>=a[i].x) 
		f[i][j]=min(f[i][j],max(f[i-1][j-a[i].x],j+a[i].y));
		//f[i-1][j-a[i].x]表示i号人打饭,i-1那个人在他打饭的时候吃不完饭
		//j+a[i].y表示i号人吃饭的时间 
		//去二号餐厅 
		f[i][j]=min(f[i][j],max(f[i-1][j],sum[i]-j+a[i].y));
		//f[i-1][j]上个人吃完饭的时间,sum[i]-j+a[i].y是这个人过来二号餐厅之后他打饭和吃饭的时间
	}
	over(i,0,sum[n]) ans=min(ans,f[n][i]); printf("%d\n",ans);
	return 0;
}

T30:

P2331 [SCOI2005]最大子矩阵
这个题,其实是重点在那个 m = 1 m=1 m=1 m = 2 m=2 m=2上面,然后就是暴力转移就好了。
f [ i ] [ j ] [ k ] : f[i][j][k]: f[i][j][k]:表示在第一行选择 i i i列在第二行 j j j列中选择了k个的最大的子矩阵
(转移下面写的很详细)
答案: f [ n ] [ n ] [ k k ] f[n][n][kk] f[n][n][kk]

代码:

#include<bits/stdc++.h>
//#define int long long
#define D double
#define mp(s,t) make_pair(s,t)
#define pii pair<int,int>
#define fir first
#define sec second
#define over(i,s,t) for(int i=s;i<=t;i++)
#define lver(i,t,s) for(int i=t;i>=s;i--)
using namespace std;
inline int read()
{
	int s=0,w=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
	return s*w;
}
const int ocean=0x7fffffff;
const int mod=1e9+7;
const int sea=1100;
const int pool=1100;
//struct see{int next,ver,edge,x;}e[sea<<1];
//int n,tot,ans,last[sea];
//void add(int x,int y,int z){e[++tot].ver=y;e[tot].x=x;e[tot].edge=z;e[tot].next=last[x];last[x]=tot;}
int n,m,kk,ans,sum[sea][2],a[sea][2],f[sea][sea][15];//f[i][j][k]就表示选择区间[i,j]中选择了k个的最大的子矩阵 
int main()
{
	n=read(); m=read(); kk=read(); over(i,1,n) over(j,1,m) a[i][j]=read();
	over(i,1,n) sum[i][1]=sum[i-1][1]+a[i][1]; over(i,1,n) sum[i][2]=sum[i-1][2]+a[i][2];
	over(k,1,kk) over(i,1,n) over(j,1,n)
	{
		//不选择的时候 
		f[i][j][k]=max(f[i][j-1][k],f[i-1][j][k]); 
		//选择第一列 
		over(l,0,i-1) f[i][j][k]=max(f[i][j][k],f[l][j][k-1]+sum[i][1]-sum[l][1]);
		//选择第二列
		over(l,0,j-1) f[i][j][k]=max(f[i][j][k],f[i][l][k-1]+sum[j][2]-sum[l][2]);
		//选择一二列 
		if(i==j) over(l,0,i-1) f[i][j][k]=max(f[i][j][k],f[l][l][k-1]+sum[i][2]-sum[l][2]+sum[i][1]-sum[l][1]);
	}
	printf("%d\n",f[n][n][kk]);
	return 0;
}

随风而逝的都是属于昨天的,历经风雨后留下来的才是面向未来的。——Blng

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值