------------------------
A poj 1088
经典水题,裸记忆化搜索
题意:在一个矩阵上,每一个格子有自己的高度,只能从高格子向周围4方向低格子移动,求最长路
分析:
2种思路
1. 进行剪枝,朴素的标记当前位置的最长路,只有当更优时,才进行dfs
实际上,这种算法很容易超时(时限1000ms)
1 /********************** 2 *@Name:A - OpenJ_Bailian - 1088 3 * 4 *@Author: Nervending 5 *@Describtion: 6 *@DateTime: 2018-01-22 15:19:01 7 ***********************/ 8 #include <bits/stdc++.h> 9 using namespace std; 10 const int maxn=1e2+10; 11 const int INF=0x3f3f3f3f; 12 int len[maxn][maxn]; 13 int g[maxn][maxn]; 14 int n,m; 15 int ans; 16 const int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}}; 17 void dfs(int nx,int ny,int step){ 18 if(step<=len[nx][ny]) return; 19 len[nx][ny]=step; 20 for(int k=0;k<4;k++){ 21 int x=nx+dir[k][0]; 22 int y=ny+dir[k][1]; 23 if(g[x][y]<g[nx][ny]&&len[x][y]<step+1){ 24 dfs(x,y,step+1); 25 } 26 } 27 } 28 29 int main(){ 30 // freopen("in.txt","r",stdin); 31 // freopen("out.txt","w",stdout); 32 33 while(~scanf("%d%d",&n,&m)){ 34 int sx,sy; 35 int mx=-INF; 36 for(int i=1;i<=n;i++){ 37 for(int j=1;j<=m;j++){ 38 scanf("%d",&g[i][j]); 39 } 40 } 41 memset(len,0,sizeof len); 42 for(int i=1;i<=n;i++){ 43 for(int j=1;j<=m;j++){ 44 dfs(i,j,1); 45 } 46 } 47 int ans=-1; 48 for(int i=1;i<=n;i++){ 49 for(int j=1;j<=m;j++){ 50 ans=max(ans,len[i][j]); 51 } 52 } 53 printf("%d ",ans); 54 } 55 return 0; 56 }
2. 转化为DAG最长路问题,记忆化搜索并标记,关键点在于每个点只需要访问1次,时间快了200倍,空间小了4倍
1 /********************** 2 *@Name:A - OpenJ_Bailian - 1088 3 * 4 *@Author: Nervending 5 *@Describtion: 6 *@DateTime: 2018-01-22 15:19:01 7 ***********************/ 8 #include <bits/stdc++.h> 9 using namespace std; 10 const int maxn=1e2+10; 11 const int INF=0x3f3f3f3f; 12 int len[maxn][maxn]; 13 int g[maxn][maxn]; 14 int n,m; 15 int ans; 16 const int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}}; 17 int dfs(int nx,int ny){ 18 if(len[nx][ny]) return len[nx][ny]; 19 int mx=1; 20 for(int k=0;k<4;k++){ 21 int x=nx+dir[k][0]; 22 int y=ny+dir[k][1]; 23 if(g[x][y]<g[nx][ny]){ 24 mx=max(dfs(x,y)+1,mx); 25 } 26 } 27 return len[nx][ny]=mx; 28 } 29 30 int main(){ 31 // freopen("in.txt","r",stdin); 32 // freopen("out.txt","w",stdout); 33 while(~scanf("%d%d",&n,&m)){ 34 memset(g,0x3f,sizeof g); 35 memset(len,0,sizeof len); 36 for(int i=1;i<=n;i++){ 37 for(int j=1;j<=m;j++){ 38 scanf("%d",&g[i][j]); 39 } 40 } 41 int ans=0; 42 for(int i=1;i<=n;i++){ 43 for(int j=1;j<=m;j++){ 44 len[i][j]=dfs(i,j); 45 ans=max(len[i][j],ans); 46 } 47 } 48 printf("%d ",ans); 49 } 50 return 0; 51 }
------------------------
B poj 1579
经典水题,裸记忆化搜索
题意:给出一个递归函数的伪代码:
1 /********************** 2 *@Name: 3 * 4 *@Author: Nervending 5 *@Describtion: 6 *@DateTime: 2018-01-22 22:51:02 7 ***********************/ 8 #include <bits/stdc++.h> 9 using namespace std; 10 const int maxn=1e2+10; 11 const int INF=0x3f3f3f3f; 12 long long dp[maxn][maxn][maxn]; 13 int a,b,c; 14 long long dfs(int a,int b,int c){ 15 if(a<=0||b<=0||c<=0) return 1; 16 if(a>20||b>20||c>20) return dfs(20,20,20); 17 if(dp[a][b][c]) return dp[a][b][c]; 18 if(a<b&&b<c) return dp[a][b][c]=dfs(a,b,c-1)+dfs(a,b-1,c-1)-dfs(a,b-1,c); 19 else return dp[a][b][c]=dfs(a-1,b,c)+dfs(a-1,b-1,c)+dfs(a-1,b,c-1)-dfs(a-1,b-1,c-1); 20 } 21 22 int main(){ 23 // freopen("in.txt","r",stdin); 24 // freopen("out.txt","w",stdout); 25 memset(dp,0,sizeof dp); 26 while(cin>>a>>b>>c){ 27 if(a==-1&&b==-1&&c==-1){ 28 break; 29 } 30 long long ans=dfs(a,b,c); 31 printf("w(%d, %d, %d) = %lld ",a,b,c,ans); 32 } 33 34 35 return 0; 36 }
------------------------
C hdu1078
经典水题,裸记忆化搜索,注意读题
题意:一个n*n的正数矩阵,从0,0点出发,每次可以向四个方向跳跃1-k步,要求落点的值大于起点的值,并获得当前点的值,
求权值最大的路
分析:
依然是DAG的记忆化搜索,每个点只访问一次即可,复杂度O(n^2) n==1e2
我写这道题时,一开始没有仔细看题,忽略了起点固定+只能直线(是否停下获取当前权值还是继续走),即使是记忆化搜索,复杂度也飙升到O(n^4*k) n==1e2,k==1e2错了很多发
1 /********************** 2 *@Name: 3 * 4 *@Author: Nervending 5 *@Describtion: 6 *@DateTime: 2018-01-22 23:03:07 7 ***********************/ 8 #include <bits/stdc++.h> 9 using namespace std; 10 const int maxn=1e2+5; 11 const int INF=0x3f3f3f3f; 12 const int dir[4][2]={1,0,-1,0,0,1,0,-1}; 13 int g[maxn][maxn]; 14 int dp[maxn][maxn]; 15 #define dx dir[d][1] 16 #define dy dir[d][0] 17 #define check(x,y) (x>=1&&y>=1&&x<=n&&y<=n) 18 int n,k; 19 int dfs(int nx,int ny){ 20 if(dp[nx][ny]) return dp[nx][ny]; 21 int len=g[nx][ny]; 22 for(int i=1;i<=k;i++){ 23 for(int d=0;d<4;d++){ 24 int x=nx+dx*i; 25 int y=ny+dy*i; 26 if(check(x,y)&&g[x][y]>g[nx][ny]) 27 len=max(len,dfs(x,y)+g[nx][ny]); 28 } 29 } 30 return dp[nx][ny]=len; 31 } 32 int main(){ 33 // freopen("in.txt","r",stdin); 34 // freopen("out.txt","w",stdout); 35 while(cin>>n>>k){ 36 if(n==-1&&k==-1) break; 37 k=min(n,k); 38 memset(g,0,sizeof g); 39 memset(dp,0,sizeof dp); 40 for(int i=1;i<=n;i++){ 41 for(int j=1;j<=n;j++){ 42 cin>>g[i][j]; 43 } 44 } 45 int ans=dfs(1,1); 46 cout<<ans<<endl; 47 } 48 49 return 0; 50 }
------------------------
D poj 3280
记忆化搜索/裸的区间DP
题意:给定一个小写字母字符串,可以在字符串任何地方插入和删除字母,每种字母有修改和删除的花费
求让字符串变成回文串的最小花费
分析:
其实是裸的区间DP,但题目时限比较松(2000ms),也可以用DAG上的记忆化搜索解决
对于dp[a][b]表示把a-b变为回文串的最小花费,不断选择是删除/添加Sa还是删除/添加Sb
需要注意的是,在前面删除Sa和在后面添加Sa,结果是一样的,也就是a被匹配了,所以只需要保存删除Sa和添加Sa中较小的一个就行
1 /********************** 2 *@Name: 3 * 4 *@Author: Nervending 5 *@Describtion: 6 *@DateTime: 2018-01-23 01:40:04 7 ***********************/ 8 #include <iostream> 9 #include <cstdio> 10 #include <algorithm> 11 #include <string.h> 12 using namespace std; 13 const int maxn=2e3+100; 14 const int INF=0x3f3f3f3f; 15 int n,m; 16 char id[maxn]; 17 int cost[maxn]; 18 int dp[maxn][maxn]; 19 int dfs(int a,int b){ 20 if(dp[a][b]!=-1) return dp[a][b]; 21 if(a==b) return dp[a][b]=0; 22 int c=(int)id[a]; 23 int d=(int)id[b]; 24 int ans; 25 if(c==d){ 26 ans=dfs(a+1,b-1); 27 }else { 28 ans=dfs(a+1,b)+cost[c]; 29 ans=min(dfs(a,b-1)+cost[d],ans); 30 } 31 return dp[a][b]=ans; 32 } 33 int main(){ 34 // freopen("in.txt","r",stdin); 35 // freopen("out.txt","w",stdout); 36 while(~scanf("%d%d%s",&n,&m,id)){ 37 memset(dp,-1,sizeof dp); 38 for(int i=0;i<n;i++){ 39 char ch[10]; 40 int a,b; 41 scanf("%s%d%d",&ch,&a,&b);; 42 cost[(int)ch[0]]=min(a,b); 43 } 44 printf("%d ",dfs(0,m-1)); 45 } 46 return 0; 47 }
若使用区间DP的非递归写法,则可以大幅度优化.
------------------------
E poj 1976
裸01背包
题意:
给一个长为n的数列,选出至多3个连续不相交的字段,要求每个字段的长度不超过m
求选出的字段的最大和
分析:
裸01背包
转化为DAG上的DFS(记忆化搜索)似乎...过不去?,一直是TLE/WA
1 /********************** 2 *@Name: 3 * 4 *@Author: Nervending 5 *@Describtion: 6 *@DateTime: 2018-01-23 03:50:55 7 ***********************/ 8 #include <bits/stdc++.h> 9 using namespace std; 10 const int maxn=2e5+10; 11 const int INF=0x3f3f3f3f; 12 int num[maxn]; 13 int sum[maxn]; 14 int dp[maxn][4]; 15 int n,m; 16 int dfs(int car,int numh){ 17 if(car>=3) return 0; 18 if(dp[car][numh]) return dp[car][numh]; 19 int ans=0; 20 for(int i=numh;i<=n;i++){ 21 ans=max(dfs(car+1,i+m)+sum[i+m]-sum[i],ans); 22 } 23 return dp[car][numh]=ans; 24 } 25 int main(){ 26 // freopen("in.txt","r",stdin); 27 // freopen("out.txt","w",stdout); 28 int casn; 29 cin>>casn; 30 while(casn--){ 31 cin>>n; 32 memset(num,0,sizeof num); 33 memset(dp,0,sizeof dp); 34 memset(sum,0,sizeof sum); 35 for(int i=1;i<=n;i++){ 36 scanf("%d",&num[i]); 37 } 38 for(int i=1;i<=maxn;i++){ 39 sum[i]=sum[i-1]+num[i]; 40 } 41 cin>>m; 42 cout<<dfs(0,0)<<endl; 43 } 44 return 0; 45 }
改成01背包,即可AC
dp[i][j]表示i个车头在前j个车中最多拉多少乘客,遍历即可
1 /********************** 2 *@Name: 3 * 4 *@Author: Nervending 5 *@Describtion: 6 *@DateTime: 2018-01-23 03:50:55 7 ***********************/ 8 #include <bits/stdc++.h> 9 using namespace std; 10 const int maxn=2e5+10; 11 const int INF=0x3f3f3f3f; 12 int num[maxn]; 13 int sum[maxn]; 14 int dp[maxn][4]; 15 int n,m; 16 int main(){ 17 //// freopen("in.txt","r",stdin); 18 // freopen("out.txt","w",stdout); 19 int casn; 20 cin>>casn; 21 while(casn--){ 22 cin>>n; 23 memset(num,0,sizeof num); 24 memset(dp,0,sizeof dp); 25 memset(sum,0,sizeof sum); 26 for(int i=1;i<=n;i++){ 27 scanf("%d",&num[i]); 28 } 29 for(int i=1;i<=maxn;i++){ 30 sum[i]=sum[i-1]+num[i]; 31 } 32 cin>>m; 33 for(int i=1;i<=3;i++){ 34 for(int j=m;j<=n;j++){ 35 int t=sum[j]-sum[j-m]; 36 dp[j][i]=max(dp[j-1][i],dp[j-m][i-1]+t); 37 } 38 } 39 cout<<dp[n][3]<<endl; 40 } 41 return 0; 42 }
------------------------
F poj 2111
裸记忆化搜索
题意;
给一个n*n矩阵,从任一点开始,有一个马,可以向周围"日"走一步,要求输出最长路并还原路径,多解输出字典序最小的一个
分析:
和A题基本一样,不过移动方式改为象棋中马的走法,并增加要求,输出字典序最小的路径,具体做法和最短路的路径还原是一样的,保留前驱节点即可
1 /********************** 2 *@Name: 3 * 4 *@Author: Nervending 5 *@Describtion: 6 *@DateTime: 2018-01-23 12:42:26 7 ***********************/ 8 #include <bits/stdc++.h> 9 using namespace std; 10 const int maxn=5e2+10; 11 const int INF=0x3f3f3f3f; 12 const int dir[8][2]={2,1,2,-1,-2,1,-2,-1,1,2,-1,2,1,-2,-1,-2}; 13 #define dx dir[d][0] 14 #define dy dir[d][1] 15 #define check(x,y) (x>=1&&y>=1&&x<=n&&y<=n) 16 int pre[maxn][maxn][2]; 17 int g[maxn][maxn]; 18 int dp[maxn][maxn]; 19 int n; 20 int dfs(int nx,int ny){ 21 if(dp[nx][ny]) return dp[nx][ny]; 22 int ans=0; 23 int tx=0,ty=0; 24 for(int d=0;d<8;d++){ 25 int x=nx+dx; 26 int y=ny+dy; 27 if(check(x,y)&&g[x][y]>g[nx][ny]){ 28 int t=dfs(x,y); 29 if(ans<t||ans==t&&g[x][y]<g[tx][ty]){ 30 tx=x; 31 ty=y; 32 ans=t; 33 } 34 } 35 } 36 if(ans){ 37 pre[nx][ny][0]=tx; 38 pre[nx][ny][1]=ty; 39 } 40 return dp[nx][ny]=ans+1; 41 } 42 43 int main(){ 44 // freopen("in.txt","r",stdin); 45 // freopen("out.txt","w",stdout); 46 while(~scanf("%d",&n)){ 47 memset(g,0x3f,sizeof g); 48 memset(dp,0,sizeof dp); 49 memset(pre,-1,sizeof pre); 50 for(int i=1;i<=n;i++){ 51 for(int j=1;j<=n;j++){ 52 scanf("%d",&g[i][j]); 53 } 54 } 55 int ans=0; 56 int sx=1,sy=1; 57 for(int i=1;i<=n;i++){ 58 for(int j=1;j<=n;j++){ 59 int t=dfs(i,j); 60 if(ans<t||ans==t&&g[sx][sy]>g[i][j]){ 61 ans=t; 62 sx=i,sy=j; 63 } 64 } 65 } 66 printf("%d ",ans); 67 while(sx!=-1){ 68 printf("%d ",g[sx][sy]); 69 int t=sx; 70 sx=pre[sx][sy][0]; 71 sy=pre[t][sy][1]; 72 } 73 } 74 return 0; 75 }
------------------------
G poj 1141
裸的区间dp,难点在于路径还原
题意:
给一个[]()组成的字符串,添加最少的[]()使其匹配,并输出一个最小解答
分析:
首先,如何获得最小花费?
首先如果一个区间的两端可以匹配,则可以转移到[l+1,r-1]这个问题
其次,无论是否匹配,都可以划分为2个子问题 [l][k]和[k+1][r]上
枚举k和外侧匹配进行比较,就可以从之前子问题的解答得到当前问题的最优解了
这时候,其实就可以写出记忆化搜索的代码了
但我们考虑线性的dp过程,
显然,最终答案是从子结构得到的,很容易发现,当前答案一定是从更小的区间得到的
因此我们把DAG分为多层,按区间长度分层
然后就可以很流畅的想到尺取法,递增的枚举区间的长度,并将窗口平移,对窗内进行状态转移即可
其次,如何获得最小解答的字符串
首先 如果对于一个子串,如果是两侧配对最优,
我们可以先输出左括号,再输出[l+1][r-1],再输出右括号
其次,如果两侧匹配的对象是新添加的一个括号,
我们可以在状态转移的时候用一个pos[l][r]保存最优解时区间添加的那个括号是在哪里
打印[l,pos[l][r],pos[l][r]+1,r]
递归的终点就是长度为1的时候,直接输出一对括号即可
1 /********************** 2 *@Name: 3 * 4 *@Author: Nervending 5 *@Describtion: 6 *@DateTime: 2018-01-23 18:49:11 7 ***********************/ 8 #include <bits/stdc++.h> 9 using namespace std; 10 const int maxn=1e3+10; 11 const int INF=0x3f3f3f3f; 12 #define check(x,y) (s[x]=='('&&s[y]==')'||s[x]=='['&&s[y]==']') 13 int n; 14 char s[maxn]; 15 int dp[maxn][maxn],pos[maxn][maxn]; 16 void output(int a,int b){ 17 if(a>b) return ; 18 if(a==b){ 19 if(s[a]=='('||s[a]==')') printf("()"); 20 else printf("[]"); 21 }else{ 22 if(pos[a][b]==-1){ 23 putchar(s[a]); 24 output(a+1,b-1); 25 putchar(s[b]); 26 }else { 27 output(a,pos[a][b]); 28 output(pos[a][b]+1,b); 29 } 30 } 31 } 32 33 int main(){ 34 // freopen("in.txt","r",stdin); 35 // freopen("out.txt","w",stdout); 36 while(gets(s)){ 37 n=strlen(s); 38 memset(dp,0,sizeof dp); 39 for(int i=1;i<n;i++){ 40 for(int j=0,k=i;k<n;j++,k++){ 41 if(check(j,k)){ 42 dp[j][k]=dp[j+1][k-1]+2; 43 pos[j][k]=-1; 44 } 45 for(int p=j;p<k;p++){ 46 if(dp[j][p]+dp[p+1][k]>=dp[j][k]){ 47 dp[j][k]=dp[j][p]+dp[p+1][k]; 48 pos[j][k]=p; 49 } 50 } 51 } 52 } 53 output(0,n-1); 54 } 55 56 return 0; 57 }
------------------------
H hdu 2848
博弈转DAG
其实并不是很需要动态规划或者记忆化搜索,是一个DAG状态图上的DFS
这道题是09年多校的一道题,并不出名,提交记录和网络上的题解也很少
我事后查了查,貌似只有代码流传...那个代码也很迷,写的很拖沓
题意:
给一个n和k k<=logn 此处log以10为底,两个人轮流操作,把数字n分为k个段,
每段为xi,然后n=sigma(xi) 并继续操作
如果无法分割为k段,判定为失败,给n和k,问先手的人是否有必胜策略
分析:
大概思路如下,定义dfs(a,b,c) a为还没分割的段落,b为已经分割的段落和,c为段落数量,
初始状态就是dfs(n,0,1),
假设当前为dfs(a,b,c)则可以状态转移到dfs(a/10,b+a%10,2) dfs(a/100,b+a%100,2)....
当c==k时 转移到!dfs(a+b,0,1) 直到结束
这个时间还可以,最大时限是1000ms,如果要优化,可以记忆化,哈希一下即可,但是因为可能的状态空间极大,遍历的重复几率却很低,所以不是很必要
1 /********************** 2 *@Name: 3 * 4 *@Author: Nervending 5 *@Describtion: 6 *@DateTime: 2018-01-24 17:21:28 7 ***********************/ 8 #include <cstdio> 9 #include <cstring> 10 #include <algorithm> 11 #include <iostream> 12 using namespace std; 13 const int maxn=1e3+10; 14 const int INF=0x3f3f3f3f; 15 #define ll long long 16 ll cmp[maxn]; 17 ll n; 18 int k; 19 bool dfs(ll a,ll b,int now){ 20 if(now==1&&a<cmp[k-1]) return false; 21 if(now==k)return !dfs(a+b,0,1); 22 for(int i=1;i<20;i++){ 23 if(a<cmp[i]) break; 24 if(dfs(a/cmp[i],b+a%cmp[i],now+1))return true; 25 } 26 return false; 27 } 28 29 int main(){ 30 // freopen("in.txt","r",stdin); 31 // freopen("out.txt","w",stdout); 32 33 34 cmp[0]=1; 35 for(int i=1;i<=19;i++){ 36 cmp[i]=cmp[i-1]*10; 37 } 38 while(~scanf("%lld%d",&n,&k)){ 39 printf("%d ",dfs(n,0,1)); 40 } 41 return 0; 42 }
------------------------
I poj 1191
刘汝佳,算法艺术与信息学竞赛(黑书)p116页的例题,99年noi的一道题
很经典的一个区间dp
中文题+经典题,题意不多赘述,可以直接点上面的题号链接
分析:
经典题,就不分析记忆化搜索的DAG形态再转化到线性dp了
我们先不用题目中的方程,考虑另外一个均方差的方程 E=(sigma(xi)^2)/n-avg^2
可以发现,最优解和sum(i,j,a,b)相关的
可以预处理所有sum(1,1,a,b) 其他子块的可以用简单的容斥定理得到
然后就是dp
怎么从记忆化的DAG搜索转化为线性DP就不在多说,先枚举次数,4重循环枚举块,再枚举横着切/竖着切的位置,即可得到答案
时间复杂度还是很满意的
1 /********************** 2 *@Name: 3 * 4 *@Author: Nervending 5 *@Describtion: 6 *@DateTime: 2018-01-23 21:56:19 7 ***********************/ 8 #include <bits/stdc++.h> 9 #define rep(x,a,b) for(int x=a;x<b;x++) 10 using namespace std; 11 const int maxn=10; 12 const int INF=0x3f3f3f3f; 13 int g[maxn][maxn]; 14 int n; 15 int sum[maxn][maxn]; 16 double avg; 17 int dp[maxn<<1][maxn][maxn][maxn][maxn]; 18 19 int main(){ 20 // freopen("in.txt","r",stdin); 21 // freopen("out.txt","w",stdout); 22 while(~scanf("%d",&n)){ 23 memset(sum,0,sizeof sum); 24 avg=0; 25 for(int i=1;i<=8;i++){ 26 for(int j=1;j<=8;j++){ 27 scanf("%d",&g[i][j]); 28 sum[i][j]=sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1]+g[i][j]; 29 avg+=g[i][j]; 30 } 31 } 32 avg=avg/(double)n; 33 rep(i,1,9) 34 rep(j,1,9) 35 rep(k,i,9) 36 rep(p,j,9){ 37 int s=sum[k][p]-sum[i-1][p]-sum[k][j-1]+sum[i-1][j-1]; 38 dp[0][i][j][k][p]=s*s; 39 } 40 for(int d=1;d<n;d++){ 41 rep(i,1,9) 42 rep(j,1,9) 43 rep(k,i,9) 44 rep(p,j,9){ 45 int t=INF; 46 rep(a,i,k){ 47 t=min(dp[d-1][i][j][a][p]+dp[0][a+1][j][k][p],t); 48 t=min(dp[0][i][j][a][p]+dp[d-1][a+1][j][k][p],t); 49 } 50 rep(b,j,p){ 51 t=min(dp[d-1][i][j][k][b]+dp[0][i][b+1][k][p],t); 52 t=min(dp[0][i][j][k][b]+dp[d-1][i][b+1][k][p],t); 53 } 54 dp[d][i][j][k][p]=t; 55 } 56 } 57 double ans=dp[n-1][1][1][8][8]; 58 ans=sqrt(ans/n-avg*avg); 59 printf("%.3lf ",ans); 60 } 61 return 0; 62 }
有问题欢迎留言