pku ppt some problem
The Triangle http://poj.org/problem?id=1163
暴力dfs的话,每个节点有两条路可以走,那么n个节点复杂度就是2^n n=100 超时 dp来做 就优化成 n^2
记忆化搜索,就能优化成n^2 因为一个点最多算一次,以后会直接返回dp i j 。 dp i j 表示这个位置能获得最大值。最后一行就是a i j ,其他行都可以由下面两条路取最大值。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define mt(a,b) memset(a,b,sizeof(a)) 5 using namespace std; 6 const int M=128; 7 int n,a[M][M],dp[M][M]; 8 int dfs(int i,int j){ 9 if(~dp[i][j]) return dp[i][j]; 10 if(i==n) dp[i][j]=a[i][j]; 11 else dp[i][j]=max(dfs(i+1,j),dfs(i+1,j+1))+a[i][j]; 12 return dp[i][j]; 13 } 14 int main(){ 15 while(~scanf("%d",&n)){ 16 for(int i=1;i<=n;i++){ 17 for(int j=1;j<=i;j++){ 18 scanf("%d",&a[i][j]); 19 } 20 } 21 mt(dp,-1); 22 printf("%d\n",dfs(1,1)); 23 } 24 return 0; 25 }
自底向上的推法,那dp i j 就表示i j 这个位置能获得的最大值, 然后dp i j 可以推向两个状态,分别是 dp i-1 j 和 dp i-1 j-1. 这是用当前状态去推能到达的所有状态的写法。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define mt(a,b) memset(a,b,sizeof(a)) 5 using namespace std; 6 const int M=128; 7 int a[M][M],dp[M][M]; 8 int main(){ 9 int n; 10 while(~scanf("%d",&n)){ 11 for(int i=1;i<=n;i++){ 12 for(int j=1;j<=i;j++){ 13 scanf("%d",&a[i][j]); 14 } 15 } 16 mt(dp,0); 17 for(int i=n+1;i>=1;i--){ 18 for(int j=1;j<=n;j++){ 19 dp[i-1][j]=max(dp[i-1][j],dp[i][j]+a[i-1][j]); 20 dp[i-1][j-1]=max(dp[i-1][j-1],dp[i][j]+a[i-1][j-1]); 21 } 22 } 23 printf("%d\n",dp[1][1]); 24 } 25 return 0; 26 }
这是用所有能到达的状态推当前状态的写法,并且空间优化了一下,省去了输入的数组。
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 const int M=128; 5 int dp[M][M]; 6 int main(){ 7 int n; 8 while(~scanf("%d",&n)){ 9 for(int i=1;i<=n;i++){ 10 for(int j=1;j<=i;j++){ 11 scanf("%d",&dp[i][j]); 12 } 13 } 14 for(int i=n-1;i>=1;i--){ 15 for(int j=1;j<=i;j++){ 16 dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+dp[i][j]; 17 } 18 } 19 printf("%d\n",dp[1][1]); 20 } 21 return 0; 22 }
最长上升子序列 http://bailian.openjudge.cn/practice/2757/
记忆化搜索
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 const int M=1024; 5 int n,a[M],dp[M]; 6 int dfs(int i){ 7 if(~dp[i]) return dp[i]; 8 dp[i]=1; 9 for(int j=i+1;j<=n;j++){ 10 if(a[i]<a[j]){ 11 dp[i]=max(dp[i],dfs(j)+1); 12 } 13 } 14 return dp[i]; 15 } 16 int main(){ 17 while(~scanf("%d",&n)){ 18 for(int i=1;i<=n;i++){ 19 scanf("%d",&a[i]); 20 dp[i]=-1; 21 } 22 int ans=0; 23 for(int i=1;i<=n;i++){ 24 ans=max(ans,dfs(i)); 25 } 26 printf("%d\n",ans); 27 } 28 return 0; 29 }
用dp【i】表示以 i 为结尾的最长上升子序列的长度,可以得到它可以由前面所有值比他小的dp +1推过来。这是当前状态由其他所有能推过来的状态更新的写法。
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 const int M=1024; 5 int a[M],dp[M]; 6 int main(){ 7 int n; 8 while(~scanf("%d",&n)){ 9 for(int i=1;i<=n;i++){ 10 scanf("%d",&a[i]); 11 dp[i]=1; 12 } 13 int ans=0; 14 for(int i=1;i<=n;i++){ 15 for(int j=1;j<=i;j++){ 16 if(a[j]<a[i]){ 17 dp[i]=max(dp[i],dp[j]+1); 18 } 19 } 20 ans=max(ans,dp[i]); 21 } 22 printf("%d\n",ans); 23 } 24 return 0; 25 }
还是用dp【i】表示以 i 为结尾的最长上升子序列的长度,由当前状态去更新其他所有能更新的状态的写法。
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 const int M=1024; 5 int a[M],dp[M]; 6 int main(){ 7 int n; 8 while(~scanf("%d",&n)){ 9 for(int i=1;i<=n;i++){ 10 scanf("%d",&a[i]); 11 dp[i]=1; 12 } 13 for(int i=1;i<=n;i++){ 14 for(int j=i+1;j<=n;j++){ 15 if(a[i]<a[j]){ 16 dp[j]=max(dp[j],dp[i]+1); 17 } 18 } 19 } 20 int ans=0; 21 for(int i=1;i<=n;i++){ 22 ans=max(ans,dp[i]); 23 } 24 printf("%d\n",ans); 25 } 26 return 0; 27 }
Common Subsequence http://poj.org/problem?id=1458
用dp i j 表示a串以 i 结尾 b串 以 j 结尾的最长公共子序列长度,这个是当前状态通过其他所有状态推来的写法。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define mt(a,b) memset(a,b,sizeof(a)) 5 using namespace std; 6 const int M=512; 7 char a[M],b[M]; 8 int dp[M][M]; 9 int main(){ 10 while(~scanf("%s%s",a,b)){ 11 mt(dp,0); 12 int n=strlen(a); 13 int m=strlen(b); 14 for(int i=1;i<=n;i++){ 15 for(int j=1;j<=m;j++){ 16 if(a[i-1]==b[j-1]){ 17 dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1); 18 } 19 else{ 20 dp[i][j]=max(dp[i-1][j],dp[i][j-1]); 21 } 22 } 23 } 24 printf("%d\n",dp[n][m]); 25 } 26 return 0; 27 }
2755:神奇的口袋 http://bailian.openjudge.cn/practice/2755/
二进制枚举,暴力。
1 #include<cstdio> 2 const int M=32; 3 int a[M]; 4 int main(){ 5 int n; 6 while(~scanf("%d",&n)){ 7 for(int i=0;i<n;i++){ 8 scanf("%d",&a[i]); 9 } 10 int all=1<<n,ans=0; 11 for(int i=0;i<all;i++){ 12 int sum=0; 13 for(int j=0;j<n;j++){ 14 if((i>>j)&1) sum+=a[j]; 15 } 16 if(sum==40) ans++; 17 } 18 printf("%d\n",ans); 19 } 20 return 0; 21 }
dfs枚举,暴力
1 #include<cstdio> 2 const int M=32; 3 int a[M],ans,n; 4 bool use[M]; 5 void dfs(int t){ 6 if(t==n){ 7 int sum=0; 8 for(int i=0;i<n;i++){ 9 if(use[i]) sum+=a[i]; 10 } 11 if(sum==40) ans++; 12 return ; 13 } 14 use[t]=true; 15 dfs(t+1); 16 use[t]=false; 17 dfs(t+1); 18 } 19 int main(){ 20 while(~scanf("%d",&n)){ 21 for(int i=0;i<n;i++){ 22 scanf("%d",&a[i]); 23 } 24 ans=0; 25 dfs(0); 26 printf("%d\n",ans); 27 } 28 return 0; 29 }
dfs直接找解,递归,不用递归sum,k-1,用了递归sum-ak,k-1
1 #include<cstdio> 2 int a[32]; 3 int dfs(int sum,int k){///return 前k个物品选和为sum的情况 4 if(sum==0) return 1; 5 if(k<=0) return 0; 6 return dfs(sum,k-1)+dfs(sum-a[k],k-1); 7 } 8 int main(){ 9 int n; 10 while(~scanf("%d",&n)){ 11 for(int i=1;i<=n;i++){ 12 scanf("%d",&a[i]); 13 } 14 printf("%d\n",dfs(40,n)); 15 } 16 return 0; 17 }
dp递推的找解,定义方式和上面一种一样,dp i j 表示前 i 个 和为 j 的情况,这个是由选和不选两种推出两个转移方程。
1 #include<cstdio> 2 #include<cstring> 3 #define mt(a,b) memset(a,b,sizeof(a)) 4 int a[32]; 5 int dp[32][64]; 6 int main(){ 7 int n; 8 while(~scanf("%d",&n)){ 9 for(int i=1;i<=n;i++){ 10 scanf("%d",&a[i]); 11 } 12 mt(dp,0); 13 dp[0][0]=1; 14 for(int i=1;i<=n;i++){ 15 for(int j=0;j<=40;j++){ 16 dp[i][j]+=dp[i-1][j]; 17 if(j+a[i]<=40) 18 dp[i][j+a[i]]+=dp[i-1][j]; 19 } 20 } 21 printf("%d\n",dp[n][40]); 22 } 23 return 0; 24 }
与上一dp相同,空间少了一维,我们只需知道某个和能达到的次数,所以输入一个个去更新所有的情况就行。这是由当前状态推向下一状态的写法。
1 #include<cstdio> 2 #include<cstring> 3 #define mt(a,b) memset(a,b,sizeof(a)) 4 int dp[64]; 5 int main(){ 6 int n,a; 7 while(~scanf("%d",&n)){ 8 mt(dp,0); 9 for(int i=1;i<=n;i++){ 10 scanf("%d",&a); 11 for(int j=40;j>=1;j--){ 12 if(dp[j]&&j+a<=40){ 13 dp[j+a]+=dp[j]; 14 } 15 } 16 dp[a]++; 17 } 18 printf("%d\n",dp[40]); 19 } 20 return 0; 21 }
end