算法竞赛入门经典(第2版)-刘汝佳-第九、十一章例题解题源码(C++语言)(部分)

来源:互联网 发布:女生香水 知乎 编辑:程序博客网 时间:2024/06/05 22:56

例题9-1

本题目指标函数的变量为时间和站的编号,指标函数为函数值为在T时刻到达n站的等待时间。有三种状态转移的方法,一种为等待1分钟,一种为搭乘右边的车,一种为搭乘左边的车,要求得d[i][j]。那么就要求得搭乘左边的车d[i+t[j-1]][j-1]和d[i+t[j]][j+1]的最优值。如此就将这个问题进行了分解。初始状态就为d[T][n]=0;最终要求得是d[0][1].

代码如下:

#include<bits/stdc++.h>using namespace std;const int INF=60000;const int maxn = 50+10;const int maxt = 200+10;int main(){//freopen("datain.txt","r",stdin);//freopen("dataout.txt","w",stdout);int kase = 0,n;while(cin>>n&&n){int T,tmp;cin>>T;int dp[maxt][maxn],t[maxn],has_train[maxt][maxn][2];memset(has_train,0,sizeof(has_train));for (int i=1;i<n;i++) cin>>t[i];cin>>tmp;for(int i=0;i<tmp;i++){int now;cin>>now;if(now<=T)has_train[now][1][0]=1;for (int j=1;j<n;j++){if(now+t[j]<=T){now+=t[j];has_train[now][j+1][0]=1;}else break;}}cin>>tmp;for(int i=0;i<tmp;i++){int now;cin>>now;if(now<=T)has_train[now][n][1]=1;for (int j=n;j>1;j--){if(now+t[j-1]<=T){now+=t[j-1];has_train[now][j-1][1]=1;}else break;}}//填表法 for (int i = 1;i<=n-1;i++) dp[T][i] = INF;//T时刻其他车站为无穷大 dp[T][n] = 0;//起点为在T时刻n车站等待时间,终点为在0时刻1车站的等待时间//这样就初始化了起点。在dp这张表里面就等于填了一行 for(int i=T-1;i>=0;i--)//填写行 for(int j=1;j<=n;j++)//填写列 {//填写列的策略是:3种方式 dp[i][j]=dp[i+1][j]+1;// 表示在i时刻需要等到1分钟才能到达i+1时刻状态 if(j<n&&has_train[i][j][0]&&i+t[j]<=T)dp[i][j]=min(dp[i][j],dp[i+t[j]][j+1]);//dp[i+t[j]][j+1] 表示从 dp[i+t[j]][j+1] 到dp[T][n]的最小等待时间 。//表示搭乘往右的火车 if(j>1&&has_train[i][j][1]&&i+t[j-1]<=T)dp[i][j]=min(dp[i][j],dp[i+t[j-1]][j-1]);//dp[i+t[j]][j+1] 表示从 dp[i+t[j]][j+1] 到dp[T][n]的最小等待时间 。//表示搭乘往左的火车}cout<<"Case Number "<<++kase<<": ";if(dp[0][1]>=INF) cout <<"impossible"<<endl;else cout<<dp[0][1]<<endl;}return 0; } 

例题9-2

本题目是典型的在转化为DAG图中的动态规划,采用记忆化搜索方式。本题的技巧在于对状态的存储。以立方体编号和使用的高的序号作为存储。另外,在输入的时候,通过对3个长度进行排序,简化了后面动态规划的选择比较。

//这道题的代码参看了书中的代码仓库 //算法:DAG上的最长路,状态为(idx, k),即当前顶面为立方体idx,其中第k条边(排序后)为高#include<cstdio>#include<cstring>#include<algorithm>using namespace std;#define REP(i,n) for(int i = 0; i < (n); i++)const int maxn = 30 + 5;int n, blocks[maxn][3], d[maxn][3];void get_dimensions(int* v, int b, int dim) {int idx = 0;  REP(i,3) if(i != dim) v[idx++] = blocks[b][i];  }int dp(int i, int j) {int& ans = d[i][j];if(ans > 0) return ans;//记忆化搜索 ans = 0;int v[2], v2[2];get_dimensions(v, i, j);//取长和宽记录在V中 REP(a,n) REP(b,3) {get_dimensions(v2, a, b);if(v2[0] < v[0] && v2[1] < v[1]) ans = max(ans, dp(a,b));//满足条件的可以进行,状态的转移。 }ans += blocks[i][j];//当没有条件进行状态转移的时候,说明已经是最后一个立方体了。//那么加上ans进行返回。 return ans;}int main() {int kase = 0;while(scanf("%d", &n) == 1 && n) {REP(i,n) {    REP(j,3) scanf("%d", &blocks[i][j]);    sort(blocks[i], blocks[i]+3);//点睛之笔,使得后来的比较非常方面 }memset(d, 0, sizeof(d));//d是全置0 int ans = 0;REP(i,n) REP(j,3) ans = max(ans, dp(i,j));//在未知起点的情况下,遍历所有起点,能够得到的最大值,选择出来。 printf("Case %d: maximum height = %d\n", ++kase, ans);}return 0;}

例题9-3

通过书中的分析,设定合适的状态,并且有明确边界,本题使用递推法,可以参照例题9-1的框架。要注意的点是顶点有1000个,要申明成全局变量才能保证空间的合理分配。

#include<bits/stdc++.h>using namespace std;const int maxn = 1000+10;double d[maxn][maxn];double dist(int x1,int y1,int x2,int y2){return sqrt(double(abs((x1-x2))*abs((x1-x2))+abs((y1-y2))*abs((y1-y2))));}int main(){//freopen("datain.txt","r",stdin);//freopen("dataout.txt","w",stdout);int n;while(cin>>n&&n){int x[maxn],y[maxn];for (int i=1;i<=n;i++) cin>>x[i]>>y[i];for (int j=1;j<n-1;j++) d[n-1][j] = dist(x[n-1],y[n-1],x[n],y[n])+dist(x[n],y[n],x[j],y[j]);//边界条件是,第一个人在n-1点,第二个人在j点,只剩下n点没有走了。 for(int i=n-2;i>1;i--) for(int j=1;j<n-1;j++){if (i>j){d[i][j]=min(d[i+1][j]+dist(x[i],y[i],x[i+1],y[i+1]),d[i+1][i]+dist(x[j],y[j],x[i+1],y[i+1]));} }printf("%.2lf\n",d[2][1]+dist(x[1],y[1],x[2],y[2]));}return 0; } 

例题9-4

本题目要注意记录和输出路径的方式,另外一个是对一些特殊输入的处理比如只有1行或者1列。初值很重要。

#include<bits/stdc++.h>using namespace std;const int maxn = 100+10;const int INF = 60000;int main(){//freopen("datain.txt","r",stdin);//freopen("dataout.txt","w",stdout);int r,c;while(cin>>r>>c&&r){int d[maxn][maxn],a[maxn][maxn],next[maxn][maxn];//输入 int ans=INF,first=0;for(int i=0;i<r;i++)for(int j=0;j<c;j++)cin>>a[i][j];//递推 for(int i=c-1;i>=0;i--) //i是列,j是行 for(int j=0;j<r;j++){if(i==c-1) d[j][c-1]=a[j][c-1];else{int row[3]={j,j-1,j+1};if(j==0) row[1]=r-1;if(j==r-1) row[2]= 0;sort(row,row+3);d[j][i] = INF;for(int k=0;k<3;k++){int v = d[row[k]][i+1]+a[j][i];if (v<d[j][i]) {d[j][i]=v; next[j][i]=row[k];}//存储路径 }}if(i==0&&d[j][i]<ans){ans = d[j][i];first=j;}//起点不固定 }cout<<first+1;for (int i=next[first][0],j=1;j<c;i=next[i][j],j++){cout<<" "<<i+1;}cout<<endl;cout<<ans<<endl;}return 0; } 


例题9-5

本题目是采用0-1背包的模型,两个优化的参数,那么在优化过程中,就要记录这两个参数。具体看代码

#include<bits/stdc++.h>using namespace std;const long long  maxn=50+10;int d[maxn][180*maxn+678];long long st[maxn][180*maxn+678];int main(){//freopen("datain.txt","r",stdin);//freopen("dataout.txt","w",stdout);int n,rnd=1;cin>>n;while(n--){int num,songs[maxn];long long t;cin>>num>>t;memset(d,0,sizeof(d));memset(st,0,sizeof(st));for (int i=1;i<=num;i++){cin>>songs[i];} for (int i=num;i>=1;i--)for(int j=0;j<=t-1;j++){d[i][j]= (i==num? 0: d[i+1][j]);st[i][j]=(i==num? 0: st[i+1][j]);if(j>=songs[i]){if(d[i][j]<d[i+1][j-songs[i]]+1){d[i][j]=d[i+1][j-songs[i]]+1;st[i][j]=st[i+1][j-songs[i]]+songs[i];}else if(d[i][j]==d[i+1][j-songs[i]]+1&&st[i+1][j-songs[i]]+songs[i]>st[i][j]){st[i][j]=st[i+1][j-songs[i]]+songs[i];}}}cout<<"Case "<<rnd++<<": "<<d[1][t-1]+1<<" ";  cout<<st[1][t-1]+678<<endl;}return 0;}

例题9-6

本题目实际上,是从几种灯泡中,选取出最优的购买方案。注意对书中状态转移方程的理解。

#include<bits/stdc++.h>using namespace std;const int maxn=10000;const int INF = 0x3f3f3f3f; struct lamp{int v,k,c,l;bool operator < (const lamp &lamp1) const{return v<lamp1.v;}};int main(){//freopen("datain.txt","r",stdin);int n;while(cin>>n&&n){lamp lamps[maxn];int d[maxn],s[maxn];for(int i=1;i<=n;i++){cin>>lamps[i].v>>lamps[i].k>>lamps[i].c>>lamps[i].l;}sort(lamps,lamps+n+1);         for (int i = 1; i <= n; i++)          {              s[i] = s[i-1]+lamps[i].l;          }          s[0]=0; d[0]=0;//表示灯泡1-i的最小开销 for (int i=1;i<=n;i++){d[i]=INF;for (int j=0;j<=i;j++) {d[i]=min(d[i],d[j]+(s[i]-s[j])*lamps[i].c+lamps[i].k);}}cout<<d[n]<<endl;}return 0;} 

例题9-7

第一段代码TLE超时,采用动态规划,时间复杂度过高。

#include<bits/stdc++.h>using namespace std;const int maxn=10000;bool isP(int i,int j,string s){int pre=i;int pos=j;bool flag=true;while(pre<pos){if(s[pre]!=s[pos]){flag=false;break;}else {pre+=1;pos-=1;}}return flag;}int main(){//freopen("datain.txt","r",stdin);//freopen("dataout.txt","w",stdout);int n;cin>>n;while(n--){int d[maxn];string s;cin>>s;int len=s.length();d[0]=0;for (int i=1;i<=len;i++){d[i]=d[i-1]+1;for (int j=1;j<=i;j++) {if(isP(j-1,i-1,s)) d[i]=min(d[i],d[j-1]+1);}}cout<<d[len]<<endl;}return 0;} 

第二段:

对代码使用C语言进行改写。AC。

#include<bits/stdc++.h>using namespace std;const int maxn=10000;bool isP(int i,int j,char s[]){while(i<j){if(s[pre]!=s[pos]) rerurn false;i++;j--;}return true;}int main(){//freopen("datain.txt","r",stdin);    int T,d[maxn];      char str[maxn];    scanf("%d", &T);      while(T--){          scanf("%s", str+1);          int len = strlen(str+1);   d[0]=0;           for(int i=1; i<=len; i++){              d[i] = d[i-1]+1;              for(int j=1; j<=i; j++)if(isP(j,i,str)){                  d[i] = min(d[i], d[j-1]+1);              }          }          printf("%d\n", d[len]);      }      return 0;  }  


例题9-8

本题目通过书中的分析,并且参考了点击打开链接的博客中的写法,状态方程都可以列出,主要是对C(i,j)的表示和,和对C(i,j)的动态更新。尤其是要注意的C(i,j)的更新与之后的是选择(i+1,j)还是(i,j+1)没有关系,只和前面选择(i-1,j)还是(i,j-1)有关系。这样就能够编写出通过C(i,j)的动态更新过程。

#include<bits/stdc++.h>using namespace std;//本代码参考了 //https://www.cnblogs.com/zyb993963526/p/6364069.html const int maxn = 5000 + 5;const int INF = 100000;char p[maxn], q[maxn];int sp[26], sq[26], ep[26], eq[26];int d[maxn][maxn], c[maxn][maxn];int main(){    //freopen("datain.txt", "r", stdin);    int T, n, m;    scanf("%d", &T);    while (T--)    {        scanf("%s%s", p + 1, q + 1);        //cout << p + 1 << " " << q + 1 << endl;        n = strlen(p + 1);        m = strlen(q + 1);        //将字母转化成数字        for (int i = 1; i <= n; i++)  p[i] -= 'A';        for (int i = 1; i <= m; i++)  q[i] -= 'A';                //预处理        for (int i = 0; i < 26; i++)        {            sp[i] = sq[i] = INF;            ep[i] = eq[i] = 0;        }                //预处理,计算出序列1中每个字符的开始位置和结束位置        for (int i = 1; i <= n; i++)        {            if(sp[p[i]]==INF) sp[p[i]]=i;            ep[p[i]] = i;        }        //预处理序列2        for (int i = 1; i <= m; i++)        {            if(sq[q[i]]==INF) sq[q[i]]= i;            eq[q[i]] = i;        }        for (int i = 0; i <= n; i++)        {            for (int j = 0; j <= m; j++)            {                if (!i && !j)  continue;                int v1 = INF, v2 = INF;                if (i)  v1 = d[i-1][j] + c[i-1][j];        //从p中取颜色                if (j)  v2 = d[i][j - 1] + c[i][j - 1];    //从q中取颜色                d[i][j] = min(v1, v2);                                //更新c数组                if (i&&d[i][j]==v1)                {                    c[i][j] = c[i - 1][j];                    if (sp[p[i]] == i && sq[p[i]] > j)  c[i][j]++;                    if (ep[p[i]] == i && eq[p[i]] <= j) c[i][j]--;                }                else if (j)                {                    c[i][j] = c[i][j - 1];                    if (sq[q[j]] == j && sp[q[j]] > i)  c[i][j]++;                    if (eq[q[j]] == j && ep[q[j]] <= i) c[i][j]--;                }            }        }            cout << d[n][m] << endl;    }    return 0;}

例题9-9

本题目按照书中的状态转移,采用记忆化搜索比较简单,但是要注意的将ans赋值INF的方式,OJ会WA。具体原因暂时不清楚,猜测可能是代码中的INF设置还是不够大导致的,所以最终采用其他形式,然后定义个vis数组来辅助进行记忆化搜索。补充:将INF设置为 const int INF=0x3f3f3f3f后AC。

#include<bits/stdc++.h>using namespace std;const int maxn=50+5;//const int INF=0xffffffff;int a[maxn],d[maxn][maxn],vis[maxn][maxn];int computed(int i,int j){if(i+1>=j) return 0;if(vis[i][j]) return d[i][j];vis[i][j]=1;int &ans=d[i][j];//ans =INF uDebug全对,但是提交不会AC ans = computed(i,i+1)+computed(i+1,j)+a[j]-a[i];for(int k=i+2;k<j;k++){int v =computed(i,k)+computed(k,j)+a[j]-a[i];if(v<ans) ans=v;}return ans;}int main(){//freopen("datain.txt","r",stdin);//freopen("dataout.txt","w",stdout);int len,n;while(cin>>len>>n&&len){for (int i=0;i<maxn;i++)for(int j=0;j<maxn;j++){vis[i][j]=0;}a[0]=0;a[n+1]=len;for(int k=1;k<=n;k++){cin>>a[k];}computed(0,n+1);cout<<"The minimum cutting is "<<d[0][n+1]<<"."<<endl;}return 0;} 

例题9-10

本题书才用书中的代码,采用递推的方式求解,采用递归的方式输出。

#include<bits/stdc++.h>using namespace std;const int maxn=100+10;int n,d[maxn][maxn];string S;bool match (char a,char b){if(a=='(')if(b==')') return true;if(a=='[')if(b==']') return true;return false;}void dp()//使用递推,确定计算顺序和边界很重要。 {for(int i=0;i<n;i++)//边界 {d[i+1][i]=0;d[i][i]=1;}for (int i=n-2;i>=0;i--)//起点从第n-2个字符开始 for (int j=i+1;j<n;j++)//终点为第n-1个字符 {d[i][j]=n;//n为最大值(实际上就是每个字符增加一个与之匹配的字符 //规则1:如果两头匹配,那么d[i][j]=d[i+1][j-1]; if(match(S[i],S[j])) d[i][j]=min(d[i][j],d[i+1][j-1]);//规则2:无论是否匹配,d[i][j]都可以分解。 for (int k=i;k<j;k++)d[i][j] = min(d[i][j],d[i][k]+d[k+1][j]);}}void print(int i,int j){if(i>j) return ;if(i==j){if(S[i]=='('||S[i]==')') printf("()");else printf("[]");return ;}int ans = d[i][j];//满足规则1的递归输出 if(match(S[i],S[j])&&ans==d[i+1][j-1]){printf("%c",S[i]);print(i+1,j-1);printf("%c",S[j]);return ;}for(int k=i;k<j;k++)//规则2递归输出 if(ans==d[i][k]+d[k+1][j]){print(i,k);print(k+1,j);return;}}int main(){//freopen("datain.txt","r",stdin);//freopen("dataout.txt","w",stdout);int m;cin>>m;getchar();while(m--){getline(cin,S);getline(cin,S);n=S.length();dp();print(0, n-1);printf("\n");if(m!=0)printf("\n");}return 0;} 

例题9-11

本题目写出状态转移方程后,理清递推的顺序,一定要注意检查不是对角线这种情况下,不能进行计算。详细的分析可以看书。

#include<bits/stdc++.h>using namespace std;const int maxn = 50+10; const int INF=0x3f3f3f3f;int x[maxn],y[maxn],m;double d[maxn][maxn];double area(int a,int b,int c)//已经构成三角形的顶点,求三角形的面积 {   double s=fabs((double)(1.0/2)*(x[a]*y[b]+x[b]*y[c]+x[c]*y[a]-x[a]*y[c]-x[b]*y[a]-x[c]*y[b]));   return s;}bool check(int a,int b,int c)//参考链接为https://www.cnblogs.com/Konjakmoyu/p/4905563.html的函数//检查是否能够连接成对角线  {     int i;     for(i=0;i<=m-1;i++)     {         if(i==a||i==b||i==c) continue;         double d=area(a,b,i)+area(a,c,i)+area(b,c,i)-area(a,b,c);         if(d<0) d=-d;         if(d<=0.01) return false;     }     return true; }int main(){//freopen("datain.txt","r",stdin);int n;cin>>n;while(n--){cin>>m;for(int i=0;i<m;i++) cin>>x[i]>>y[i];//确定边界for (int i=0;i<m-1;i++){d[i][i+1]=0.0;d[i][i]=0.0;}for(int j=2;j<m;j++)//一定要理清递推的顺序 for(int i=j-2;i>=0;i--){d[i][j]=INF;double ans;for(int k=i+1;k<j;k++){if(area(i,j,k)>0.1){if(check(i,j,k)){ans=max(area(i,j,k),d[i][k]);ans=max(ans,d[k][j]);d[i][j]=min(d[i][j],ans);}}}}printf("%.1lf\n",d[0][m-1]);}return 0;} 

例题9-12

本题目的状态比较简单,因为是树上的动态规划,使用了vector来进行辅助,采用了递归的方式,进行求解,通过排序和循环来选择最小的几个的值。写法值得借鉴。

#include<bits/stdc++.h>using namespace std;const int maxn=100000+10;int N,T;vector<int> sons[maxn];int dp(int u){if(sons[u].empty()) return 1;int k=sons[u].size();vector<int> d;for(int i=0;i<k;i++)d.push_back(dp(sons[u][i]));//儿子的需要的票数都存在vector里面//本次的dp通过递归实现 sort(d.begin(),d.end());//通过排序选择最少的票数 int c =(k*T-1)/100+1;int ans=0;for(int i=0;i<c;i++) ans+=d[i];return ans;}int main(){while(cin>>N>>T&&T){for(int i=0;i<maxn;i++)sons[i].clear();for(int i=1;i<=N;i++){int j;cin>>j;sons[j].push_back(i);}cout<<dp(0)<<endl;}return 0;} 

例题9-13

本题目按照书中的思路,和9-12例题的代码结合,唯一注意的时,每次计算完之后,vector和map需要进行清空。

#include<bits/stdc++.h>using namespace std;const int maxn=200+10;map<string,int> n2id;vector<int> sons[maxn];int d[maxn][3];bool f[maxn][3];void dp(int u){if(sons[u].empty()){d[u][0]=0;d[u][1]=1;return ;//0表示不唯一,1表示唯一}int k=sons[u].size();for (int i=0;i<k;i++){dp(sons[u][i]);} for (int i=0;i<k;i++){d[u][1]+=d[sons[u][i]][0];if(f[sons[u][i]][0]!=1)f[u][1]=0;if(d[sons[u][i]][0]>d[sons[u][i]][1]){d[u][0]+=d[sons[u][i]][0];if(!f[sons[u][i]][0]) f[u][0]=0;}else{d[u][0]+=d[sons[u][i]][1];if(d[sons[u][i]][0]==d[sons[u][i]][1]||!f[sons[u][i]][1]) f[u][0]=0;}}d[u][1]++; }int main(){//freopen("datain.txt","r",stdin);//freopen("dataout.txt","w",stdout);int n;while(cin>>n&&n){memset(d,0,sizeof(d));memset(f,1,sizeof(f));int id = 0;string boss,s1,s2;cin>>boss;n2id[boss] = id++;for(int i=1;i<n;i++){cin>>s1>>s2;if(!n2id.count(s1))n2id[s1]=id++;if(!n2id.count(s2))n2id[s2]=id++;sons[n2id[s2]].push_back(n2id[s1]);}dp(0);if(d[0][1]==d[0][0])  cout<<d[0][1]<<" No"<<endl;        else if(d[0][1]>d[0][0]) {cout<<d[0][1]<<" ";if(f[0][1])cout<<"Yes";else cout<<"No";cout<<endl;}        else{cout<<d[0][0]<<" ";if(f[0][0])cout<<"Yes";else cout<<"No";cout<<endl;}          for(int i = 0;i<n;i++)              sons[i].clear();          n2id.clear();  }return 0;}




阅读全文
0 0