万物皆可dp
鉴于我的dp如此之水,我决定在noip之前刷一刷dp水题。从luogu开始
1.P1004 方格取数
大意:设有N×N的方格图)(N≤9),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字0。如下图所示(见样例):
某人从图的左上角的A点出发,可以向下行走,也可以向右走,直到到达右下角的B点。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。
此人从A点到B点共走两次,试找出2条这样的路径,使得取得的数之和为最大。
题解:一个4维dp,dp[ i ][ j ][ k ][ t ]表示第一次走到( i,j )第二次走到( k,t )的最大和,如果这两个点一样,dp数组就减去a[ i ][ j ],转移方程也很好写。

#include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define ll long long const int maxn=15; int dp[maxn][maxn][maxn][maxn],n,a[maxn][maxn],aa,bb,cc; template<typename T>void read(T& aa){ char cc; ll ff;aa=0;cc=getchar();ff=1; while((cc<'0'||cc>'9')&&cc!='-') cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } int maxx(int a,int b,int c,int d){ return max(max(max(a,b),c),d); } int main(){ read(n); while(scanf("%d%d%d",&aa,&bb,&cc)&&(aa||bb||cc)) a[aa][bb]=cc; dp[1][1][1][1]=a[1][1]; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) for(int k=1;k<=n;k++) for(int l=1;l<=n;l++){ dp[i][j][k][l]=maxx(dp[i-1][j][k-1][l],dp[i-1][j][k][l-1],dp[i][j-1][k-1][l],dp[i][j-1][k][l-1])+a[i][j]+a[k][l]; if(i==k&&j==l) dp[i][j][k][l]-=a[i][j]; } cout<<dp[n][n][n][n]<<endl; return 0; }
2.P2733 家的范围 Home on the Range
大意:农民约翰在一片边长是N (2 <= N <= 250)英里的正方形牧场上放牧他的奶牛。(因为一些原因,他的奶牛只在正方形的牧场上吃草。)遗憾的是,他的奶牛已经毁坏一些土地。( 一些1平方英里的正方形)
农民约翰需要统计那些可以放牧奶牛的正方形牧场(至少是2x2的,在这些较大的正方形中没有一个点是被破坏的,也就是说,所有的点都是“1”)。
你的工作要在被供应的数据组里面统计所有不同的正方形放牧区域(>=2x2)的个数。当然,放牧区域可能是重叠。
题解:跟最大正方形相似,dp[ i ][ j ]表示以( i,j )为右下角的最大正方形的边长,那么dp[ i ][ j ] = min { dp[ i-1 ][ j ],dp[ i ][ j-1 ],dp[ i-1 ][ j-1 ] }。至于记录个数,每算出一个dp[ i ][ j ],就把num[ dp[ i ][ j ] ] ++,注意,最终num数组并不是答案,因为一个大正方形里是可以包含小正方形的,所以我们需要把小正方形的个数加上比它大的正方形的个数。

#include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define ll long long const int maxn=250+50; int n,dp[maxn][maxn],num[maxn]; char s[maxn][maxn]; int minn(int a,int b,int c){ return min(min(a,b),c); } template<typename T>void read(T& aa){ char cc; ll ff;aa=0;cc=getchar();ff=1; while((cc<'0'||cc>'9')&&cc!='-') cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } int main(){ read(n); for(int i=1;i<=n;i++) cin>>s[i]+1; for(int i=1;i<=n;i++) dp[i][1]=(s[i][1]=='1'); for(int i=1;i<=n;i++) dp[1][i]=(s[1][i]=='1'); for(int i=2;i<=n;i++) for(int j=2;j<=n;j++){ if(s[i][j]=='1'){ dp[i][j]=minn(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1; if(dp[i][j]>=2) num[dp[i][j]]++; } } for(int i=n;i>0;i--) num[i-1]+=num[i]; for(int i=2;i<=n;i++) if(num[i]) cout<<i<<" "<<num[i]<<endl; return 0; }
3.P1816 忠诚
大意:老管家是一个聪明能干的人。他为财主工作了整整10年,财主为了让自已账目更加清楚。要求管家每天记k次账,由于管家聪明能干,因而管家总是让财主十分满意。但是由于一些人的挑拨,财主还是对管家产生了怀疑。于是他决定用一种特别的方法来判断管家的忠诚,他把每次的账目按1,2,3…编号,然后不定时的问管家问题,问题是这样的:在a到b号账中最少的一笔是多少?为了让管家没时间作假他总是一次问多个问题。
题解:RMQ裸题,又水了一道哈哈哈。

#include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define ll long long const int maxn=100000+50; int m,n,a[maxn],x,y,mn[maxn][25]; struct RMQ{ int log2[maxn]; void init(){ for(int i=0;i<=n;i++) log2[i]=(i==0?-1:log2[i>>1]+1); for(int j=1;j<20;j++) for(int i=1;i+(1<<j)<=n+1;i++) mn[i][j]=min(mn[i][j-1],mn[i+(1<<j-1)][j-1]); } int query(int ql,int qr) { int k=log2[qr-ql+1]; return min(mn[ql][k], mn[qr-(1<<k)+1][k]); } }rmq; template<typename T>void read(T& aa){ char cc; ll ff;aa=0;cc=getchar();ff=1; while((cc<'0'||cc>'9')&&cc!='-') cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } int main(){ // freopen("xxx.in","r",stdin); // freopen("xxx.out","w",stdout); read(n),read(m); for(int i=1;i<=n;i++) read(a[i]),mn[i][0]=a[i]; rmq.init(); while(m--){ read(x),read(y); cout<<rmq.query(x,y)<<" "; } return 0; }
4.P1280 尼克的任务
大意:尼克要工作n分钟,共有k个任务,每个任务有个开始时刻s和持续时间t,如果在某一时刻尼克有空且只有一个任务,那么尼克做;如果有多个任务,那么尼克选一个做,剩下的交给同事,求尼克可以获得的最长休息时间。
题解:设dp[ i ]为 i 时刻起还能休息的最长时间。记录某时刻的工作数,从n到1倒推,如果此时工作数等于0,那么休息时间+1;否则dp[ i ] = max( dp[ i ],dp[ i+t[i] ] )。

#include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define ll long long const int maxn=10000+100; int n,k,dp[maxn],sum[maxn]; struct node{ int s,e; }a[maxn]; template<typename T>void read(T& aa) { char cc; ll ff;aa=0;cc=getchar();ff=1; while((cc<'0'||cc>'9')&&cc!='-') cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } int cmp(const node &a,const node &b){ return a.s>b.s; } int main(){ read(n),read(k); for(int i=1;i<=k;i++){ read(a[i].s),read(a[i].e); sum[a[i].s]++; } sort(a+1,a+1+n,cmp); for(int i=n;i>=1;i--){ if(sum[i]==0) dp[i]=dp[i+1]+1; else for(int j=1;j<=k;j++) if(i==a[j].s) dp[i]=max(dp[i],dp[i+a[j].e]); } cout<<dp[1]<<endl; return 0; }
5.P1279 字串距离
大意:
设有字符串X,我们称在X的头尾及中间插入任意多个空格后构成的新字符串为X的扩展串,如字符串X为”abcbcd”,则字符串“abcb□cd”,“□a□bcbcd□”和“abcb□cd□”都是X的扩展串,这里“□”代表空格字符。
如果A1是字符串A的扩展串,B1是字符串B的扩展串,A1与B1具有相同的长度,那么我扪定义字符串A1与B1的距离为相应位置上的字符的距离总和,而两个非空格字符的距离定义为它们的ASCII码的差的绝对值,而空格字符与其他任意字符之间的距离为已知的定值K,空格字符与空格字符的距离为0。在字符串A、B的所有扩展串中,必定存在两个等长的扩展串A1、B1,使得A1与B1之间的距离达到最小,我们将这一距离定义为字符串A、B的距离。
请你写一个程序,求出字符串A、B的距离。
题解:有三种情况:A[ i ]与B[ j ] 配对,A[ i ] 与空格配对,B[ i ]与空格配对。设dp[ i ][ j ]为A串匹配到 i , B串匹配到 j 的最小距离,那么分类:
若A[ i ]与B[ j ] 配对: dp[ i ][ j ] = dp[ i-1 ][ j-1 ] + abs( A[ i ] - B[ i ] )
若A[ i ]与空格配对: dp[ i ][ j ] = dp[ i-1 ][ j ] + k
若B[ i ]与空格配对: dp[ i ][ j ] = dp[ i ][ j-1 ] + k
然后三个取min即可。预处理:dp[ i ][ 0 ] = dp[ i-1 ][ 0 ] + k , dp[ 0 ][ j ] = dp[ 0 ][ j-1 ] + k 。

#include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define ll long long const int maxn=2000+50; int k,la,lb,dp[maxn][maxn]; char a[maxn],b[maxn]; template<typename T>void read(T& aa){ char cc; ll ff;aa=0;cc=getchar();ff=1; while((cc<'0'||cc>'9')&&cc!='-') cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } int main(){ cin>>a+1>>b+1; read(k); la=strlen(a+1); lb=strlen(b+1); for(int i=1;i<=la;i++) dp[i][0]=dp[i-1][0]+k; for(int i=1;i<=lb;i++) dp[0][i]=dp[0][i-1]+k; for(int i=1;i<=la;i++) for(int j=1;j<=lb;j++){ dp[i][j]=min(dp[i-1][j]+k,min(dp[i][j-1]+k,dp[i-1][j-1]+abs((int)a[i]-(int)b[j]))); } cout<<dp[la][lb]<<endl; return 0; }
6.P1523 旅行商简化版
大意:现在笛卡尔平面上有n(n<=1000)个点,每个点的坐标为(x,y)(-2^31<x,y<2^31,且为整数),任意两点之间相互到达的代价为这两点的欧几里德距离,现要你编程求出最短bitonic tour。
题解:从左到右再回来,我们可以简化成有两个人开始都在左边,他们要走不同的点到达最右边,且总距离最短。首先将坐标按x轴从小到大排序,设dp[ i ][ j ]表示第一个人走到了 i 点,第二个人走到了 j 点,还需要走的最短距离,由于dp[ i ][ j ] == dp[ j ][ i ],我们不妨设 i > j 。如果 i = n-1,那么下一步就要到 n 点( 终点 )了,这两个人都必须马上走到 n 点,即 dp[ i ][ j ] = dis[ i ][ n ] + dis[ j ][ n ] ( dis[ i ][ j ] 为 i到 j 的距离,可预处理出 ) 。如果 i ≠ n-1,那么下一步是走到 i+1 ,可以是 第一个人走:dp[ i ][ j ] = dp[ i+1 ][ j ] + dis[ i ][ i+1 ] ;也可以是第二个人走:dp[ i ][ j ] = dp[ i+1 ][ i ] + dis[ j ][ i+1 ],然后将这两种情况取min。最终答案是 dp[ 2 ][ 1 ] + dis[ 1 ][ 2 ] 。

#include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define ll long long const int maxn=1000+50; int n; double dp[maxn][maxn],dis[maxn][maxn]; struct node{int x,y;}a[maxn]; double dist(int p,int q){ double dx=double(a[p].x-a[q].x); double dy=double(a[p].y-a[q].y); return sqrt(dx*dx+dy*dy); } int cmp(const node &a,const node &b){return a.x<b.x;} template<typename T>void read(T& aa){ char cc; ll ff;aa=0;cc=getchar();ff=1; while((cc<'0'||cc>'9')&&cc!='-') cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } int main(){ read(n); for(int i=1;i<=n;i++) read(a[i].x),read(a[i].y); sort(a+1,a+1+n,cmp); for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) dis[i][j]=dis[j][i]=dist(i,j); for(int i=n-1;i>=2;i--) for(int j=1;j<i;j++){ if(i==n-1) dp[i][j]=dis[i][n]+dis[j][n]; else dp[i][j]=min(dp[i+1][j]+dis[i][i+1],dp[i+1][i]+dis[j][i+1]); } printf("%.2lf ",dp[2][1]+dis[1][2]); return 0; }
7.P1541 乌龟棋
大意:
乌龟棋的棋盘是一行N个格子,每个格子上一个分数(非负整数)。棋盘第1格是唯一的起点,第NN格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点。
乌龟棋中MM张爬行卡片,分成4种不同的类型(M张卡片中不一定包含所有4种类型的卡片,见样例),每种类型的卡片上分别标有1,2,3,4四个数字之一,表示使用这种卡片后,乌龟棋子将向前爬行相应的格子数。游戏中,玩家每次需要从所有的爬行卡片中选择一张之前没有使用过的爬行卡片,控制乌龟棋子前进相应的格子数,每张卡片只能使用一次。
游戏中,乌龟棋子自动获得起点格子的分数,并且在后续的爬行中每到达一个格子,就得到该格子相应的分数。玩家最终游戏得分就是乌龟棋子从起点到终点过程中到过的所有格子的分数总和。
很明显,用不同的爬行卡片使用顺序会使得最终游戏的得分不同,小明想要找到一种卡片使用顺序使得最终游戏得分最多。
现在,告诉你棋盘上每个格子的分数和所有的爬行卡片,你能告诉小明,他最多能得到多少分吗?
题解:第一眼发现有点像背包,先记录每个棋子出现的次数cnt[ i ],依次枚举4种棋子出现的次数 i,j,k,t,那么应该到的格子就是 1+ i + j*2 + k*3 + t*4 。设 r =1+ i + j*2 + k*3 + t*4 ,设dp[ i ][ j ][ k ][ t ]为四个棋子分别使用 i j k t 次的最大分数。转移方程:
dp[ i ][ j ][ k ][ t ] = max { dp[ i-1 ][ j ][ k ][ t ] ,dp[ i ][ j-1 ][ k ][ t ] ,dp[ i ][ j ][ k-1 ][ t ] ,dp[ i ][ j ][ k ][ t-1 ] } + a[ r ] , 其中 a[ r ]为 r 处的分数。

#include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define ll long long const int maxn=350+50; const int maxm=120+50; int a[maxn],b[maxm],n,m,dp[50][50][50][50],cnt[5]; template<typename T>void read(T& aa){ char cc; ll ff;aa=0;cc=getchar();ff=1; while((cc<'0'||cc>'9')&&cc!='-') cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } int main(){ read(n),read(m); for(int i=1;i<=n;i++) read(a[i]); for(int i=1;i<=m;i++) read(b[i]),cnt[b[i]]++; dp[0][0][0][0]=a[1]; for(int i=0;i<=cnt[1];i++) for(int j=0;j<=cnt[2];j++) for(int k=0;k<=cnt[3];k++) for(int t=0;t<=cnt[4];t++){ int r=i*1+j*2+k*3+t*4+1; if(i!=0) dp[i][j][k][t]=max(dp[i][j][k][t],dp[i-1][j][k][t]+a[r]); if(j!=0) dp[i][j][k][t]=max(dp[i][j][k][t],dp[i][j-1][k][t]+a[r]); if(k!=0) dp[i][j][k][t]=max(dp[i][j][k][t],dp[i][j][k-1][t]+a[r]); if(t!=0) dp[i][j][k][t]=max(dp[i][j][k][t],dp[i][j][k][t-1]+a[r]); } cout<<dp[cnt[1]][cnt[2]][cnt[3]][cnt[4]]<<endl; return 0; }
8.P1725 琪露诺
大意:
在幻想乡,琪露诺是以笨蛋闻名的冰之妖精。
某一天,琪露诺又在玩速冻青蛙,就是用冰把青蛙瞬间冻起来。但是这只青蛙比以往的要聪明许多,在琪露诺来之前就已经跑到了河的对岸。于是琪露诺决定到河岸去追青蛙。
小河可以看作一列格子依次编号为0到N,琪露诺只能从编号小的格子移动到编号大的格子。而且琪露诺按照一种特殊的方式进行移动,当她在格子i时,她只移动到区间[i+l,i+r]中的任意一格。你问为什么她这么移动,这还不简单,因为她是笨蛋啊。
每一个格子都有一个冰冻指数A[i],编号为0的格子冰冻指数为0。当琪露诺停留在那一格时就可以得到那一格的冰冻指数A[i]。琪露诺希望能够在到达对岸时,获取最大的冰冻指数,这样她才能狠狠地教训那只青蛙。
但是由于她实在是太笨了,所以她决定拜托你帮它决定怎样前进。
开始时,琪露诺在编号0的格子上,只要她下一步的位置编号大于N就算到达对岸。N <= 200,000
题解:转移简单,dp[ i ] = max( dp[ j ] ) + a[ i ] i-R ≤ j ≤ i-L 。但是如果循环找最大值,那么时间复杂度是过不去的,于是我们考虑优化。注意到每次只会在特定的区间取最大值,就像一个滑动窗口,于是我们可以用单调队列维护区间的最大值。

#include<deque> #include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define ll long long const int maxn=200000+50; const int inf=0x3f3f3f3f; struct node{int val,id;}tmp; int dp[maxn],a[maxn],n,l,r,p; deque <node> dq; template<typename T>void read(T& aa){ char cc; ll ff;aa=0;cc=getchar();ff=1; while((cc<'0'||cc>'9')&&cc!='-') cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } int main(){ read(n),read(l),read(r); for(int i=0;i<=n;i++) read(a[i]); p=0,dp[p]=0; for(int i=l;i<=n;i++){ while(!dq.empty()&&dp[p]>=dq.back().val) dq.pop_back(); tmp.id=p; tmp.val=dp[p]; dq.push_back(tmp); if(p-dq.front().id>=(r-l+1)) dq.pop_front(); dp[i]=dq.front().val+a[i]; p++; } int ans=-inf; for(int i=n-r+1;i<=n;i++) ans=max(ans,dp[i]); cout<<ans<<endl; return 0; }
9.P1799 数列_NOI导刊2010提高(06)
大意:虽然msh长大了,但她还是很喜欢找点游戏自娱自乐。有一天,她在纸上写了一串数字:1,l,2,5,4。接着她擦掉了一个l,结果发现剩下l,2,4都在自己所在的位置上,即1在第1位,2在第2位,4在第4位。她希望擦掉某些数后,剩下的数列中在自己位置上的数尽量多。她发现这个游戏很好玩,于是开始乐此不疲地玩起来……不过她不能确定最多能有多少个数在自己的位置上,所以找到你,请你帮忙计算一下!n≤l000
题解:对于一个位置 i ,只有删去它前面的数才会对它造成影响,于是设dp[ i ][ j ]表示前 i 个数剩下 j 个数的最优解。枚举 j ,如果a[ i ] = j,dp[ i ][ j ] = max( dp[ i-1 ][ j ] ,dp[ i-1 ][ j-1 ] + 1 ) ; 否则 dp[ i ][ j ] = max( dp[ i-1 ][ j ] ,dp[ i-1 ][ j-1 ] ) 。

#include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define ll long long const int maxn=1000+50; int n,a[maxn],dp[maxn][maxn]; template<typename T>void read(T& aa){ char cc; ll ff;aa=0;cc=getchar();ff=1; while((cc<'0'||cc>'9')&&cc!='-') cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } int main(){ read(n); for(int i=1;i<=n;i++) read(a[i]); for(int i=1;i<=n;i++) for(int j=i;j>=1;j--){ if(a[i]==j) dp[i][j]=max(dp[i-1][j],dp[i-1][j-1]+1); else dp[i][j]=max(dp[i-1][j],dp[i-1][j-1]); } int ans=0; for(int i=1;i<=n;i++) ans=max(ans,dp[n][i]); cout<<ans<<endl; return 0; }
10.P2327 [SCOI2005]扫雷
大意:相信大家都玩过扫雷的游戏。那是在一个 n×m的矩阵里面有一些雷,要你根据一些信息找出雷来。万圣节到了,“余”人国流行起了一种简单的扫雷游戏,这个游戏规则和扫雷一样,如果某个格子没有雷,那么它里面的数字表示和它8连通的格子里面雷的数目。现在棋盘是 n×2的,第一列里面某些格子是雷,而第二列没有雷,如下图:
题解:还是较水。dp[ a ][ b ][ c ][ d ] 表示在 a 位置时,a-1,a,a+1位置的状态 ( 0表示无雷,1表示有雷 )的方案数。转移太长就不写了,注意右边数有为0的情况。

#include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define ll long long const int maxn=10000+50; int a[maxn],n,dp[maxn][2][2][2]; template<typename T>void read(T& aa){ char cc; ll ff;aa=0;cc=getchar();ff=1; while((cc<'0'||cc>'9')&&cc!='-') cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } int main(){ read(n); for(int i=1;i<=n;i++) read(a[i]); if(a[1]==3){ cout<<0<<endl; return 0; } dp[0][0][0][0]=dp[0][0][0][1]=1; for(int i=1;i<=n;i++){ if(a[i]==0) dp[i][0][0][0]=dp[i-1][0][0][0]+dp[i-1][1][0][0]; if(a[i]==1){ dp[i][1][0][0]=dp[i-1][1][1][0]+dp[i-1][0][1][0]; dp[i][0][1][0]=dp[i-1][0][0][1]+dp[i-1][1][0][1]; dp[i][0][0][1]=dp[i-1][1][0][0]+dp[i-1][0][0][0]; } if(a[i]==2){ dp[i][1][1][0]=dp[i-1][1][1][1]+dp[i-1][0][1][1]; dp[i][1][0][1]=dp[i-1][1][1][0]+dp[i-1][0][1][0]; dp[i][0][1][1]=dp[i-1][1][0][1]+dp[i-1][0][0][1]; } if(a[i]==3) dp[i][1][1][1]=dp[i-1][1][1][1]+dp[i-1][0][1][1]; } if(a[n]==1) cout<<dp[n][1][0][0]+dp[n][0][1][0]<<endl; if(a[n]==2) cout<<dp[n][1][1][0]<<endl; if(a[n]==3) cout<<0<<endl; if(a[n]==0) cout<<dp[n][0][0][0]<<endl; return 0; }
11.P1122 最大子树和
大意:一株奇怪的花卉,上面共连有N朵花,共有N-1条枝干将花儿连在一起,并且未修剪时每朵花都不是孤立的。每朵花都有一个“美丽指数”,该数越大说明这朵花越漂亮,也有“美丽指数”为负数的,说明这朵花看着都让人恶心。所谓“修剪”,意为:去掉其中的一条枝条,这样一株花就成了两株,扔掉其中一株。经过一系列“修剪“之后,还剩下最后一株花(也可能是一朵)。老师的任务就是:通过一系列“修剪”(也可以什么“修剪”都不进行),使剩下的那株(那朵)花卉上所有花朵的“美丽指数”之和最大。
题解:其实就是dfs,对于每个结点,就把它儿子中的美丽指数大于0的加起来,然后对每个结点统计答案。

#include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define ll long long const int maxn=16000+50; int fir[maxn],nex[maxn*2],to[maxn*2],ecnt; int n,val[maxn],ans,a,b,dp[maxn]; void add_edge(int u,int v){ nex[++ecnt]=fir[u];fir[u]=ecnt;to[ecnt]=v; } template<typename T>void read(T& aa){ char cc; ll ff;aa=0;cc=getchar();ff=1; while((cc<'0'||cc>'9')&&cc!='-') cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } void dfs(int x,int f){ dp[x]=val[x]; for(int e=fir[x];e;e=nex[e]){ int v=to[e]; if(v==f) continue; dfs(v,x); dp[x]+=max(0,dp[v]); } ans=max(ans,dp[x]); } int main(){ read(n); for(int i=1;i<=n;i++) read(val[i]); for(int i=1;i<n;i++){ read(a),read(b); add_edge(a,b); add_edge(b,a); } dfs(1,0); cout<<ans; return 0; }
12.P2280 [HNOI2003]激光炸弹
大意:一种新型的激光炸弹,可以摧毁一个边长为R的正方形内的所有的目标。现在地图上有n(N<=10000)个目标,用整数Xi,Yi(其值在[0,5000])表示目标在地图上的位置,每个目标都有一个价值。激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆破范围,即那个边长为R的正方形的边必须和x,y轴平行。若目标位于爆破正方形的边上,该目标将不会被摧毁。
题解:首先暴力求二维前缀和,然后只需要在每个半径为r 的正方形里找最大的就ok了。

#include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define ll long long const int maxn=5000+5; int v[maxn][maxn],n,r,x,y,z,ans; template<typename T>void read(T& aa){ char cc; ll ff;aa=0;cc=getchar();ff=1; while((cc<'0'||cc>'9')&&cc!='-') cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } int main(){ read(n),read(r); for(int i=1;i<=n;i++){ read(x),read(y),read(z); v[x+1][y+1]=z; } for(int i=1;i<=5001;i++) for(int j=1;j<=5001;j++) v[i][j]+=v[i][j-1]+v[i-1][j]-v[i-1][j-1]; for(int i=0;i<=5001-r;i++) for(int j=0;j<=5001-r;j++) ans=max(ans,v[i+r][j+r]-v[i][j+r]-v[i+r][j]+v[i][j]); cout<<ans<<endl; return 0; }
13.P2380 狗哥采矿
大意:一个n*m的矩阵中,每个格子内有两种矿yeyenum和bloggium,并且知道它们在每个格子内的数量是多少。最北边有bloggium的收集站,最西边有 yeyenum 的收集站。现在要你在这些格子上面安装向北或者向西的传送带(每个格子只能装一种)。问最多能采到多少矿?
题解:以前考试考过这题,当时以为很简单随便贪心了一下于是光荣爆0。dp[ i ][ j ]表示取到 (i,j)最多能采到多少矿,于是dp[ i ][ j ] = max( dp[ i-1 ][ j ] + hang[ i ][ j ] , dp[ i ][ j -1 ] + lie[ i ][ j ] )。其中hang,lie是前缀和。

#include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define ll long long const int maxn=1000; int dp[maxn][maxn],n,m,le[maxn][maxn],ha[maxn][maxn]; int a,b; template<typename T>void read(T& aa){ char cc; ll ff;aa=0;cc=getchar();ff=1; while((cc<'0'||cc>'9')&&cc!='-') cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } int main(){ while(scanf("%d%d",&n,&m)==2&&(n||m)){ memset(dp,0,sizeof(dp)); memset(ha,0,sizeof(ha)); memset(le,0,sizeof(le)); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ read(a); ha[i][j]=ha[i][j-1]+a; } for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ read(b); le[i][j]=le[i-1][j]+b; } for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ dp[i][j]=max(dp[i-1][j]+ha[i][j],dp[i][j-1]+le[i][j]); } cout<<dp[n][m]<<endl; } return 0; }
14.P1434 [SHOI2002]滑雪
大意:
Michael喜欢滑雪。这并不奇怪,因为滑雪的确很刺激。可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你。Michael想知道在一个区域中最长的滑坡。区域由一个二维数组给出。数组的每个数字代表点的高度。下面是一个例子:
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小。在上面的例子中,一条可行的滑坡为24-17-16-1(从24开始,在1结束)。当然25-24-23―┅―3―2―1更长。事实上,这是最长的一条。
题解:记忆化搜索的入门题。每个点搜一下,记录最大值就可以了。

#include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; #define ll long long int fx[4]={0,1,0,-1}; int fy[4]={1,0,-1,0}; const int maxn=100+50; int R,C,a[maxn][maxn],Max,dp[maxn][maxn]; int dfs(int x,int y){ if(dp[x][y]!=-1) return dp[x][y]; int ans=0; for(int i=0;i<4;i++){ int xx=x+fx[i]; int yy=y+fy[i]; if(xx<=R&&xx>0&&yy<=C&&yy>0&&a[xx][yy]<a[x][y]){ ans=max(ans,dfs(xx,yy)+1); } } dp[x][y]=ans; return ans; } template<typename T>void read(T& aa){ char cc; ll ff;aa=0;cc=getchar();ff=1; while((cc<'0'||cc>'9')&&cc!='-') cc=getchar(); if(cc=='-') ff=-1,cc=getchar(); while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar(); aa*=ff; } int main(){ memset(dp,-1,sizeof(dp)); read(R),read(C); for(int i=1;i<=R;i++) for(int j=1;j<=C;j++) read(a[i][j]); for(int i=1;i<=R;i++) for(int j=1;j<=C;j++){ Max=max(Max,dfs(i,j)); } cout<<Max+1; return 0; }