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

例题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
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>t[i];
		cin>>tmp;
		for(int i=0;i>now;
			if(now<=T)
			has_train[now][1][0]=1;
			for (int j=1;j>tmp;
		for(int i=0;i>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(j1&&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"<

例题9-2

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

//这道题的代码参看了书中的代码仓库 
//算法:DAG上的最长路,状态为(idx, k),即当前顶面为立方体idx,其中第k条边(排序后)为高
#include
#include
#include
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
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;j1;i--) 
			for(int j=1;jj)
				{
					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
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>a[i][j];
	
		//递推 
		for(int i=c-1;i>=0;i--) //i是列,j是行 
			for(int j=0;j


例题9-5

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

#include
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]st[i][j])
					{
						st[i][j]=st[i+1][j-songs[i]]+songs[i];
					}
				}
					
			}
		cout<<"Case "<

例题9-6

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

#include
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>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<

例题9-7

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

#include
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>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<

第二段:

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

#include
using namespace std;
const int maxn=10000;
bool isP(int i,int j,char s[])
{
	while(i


例题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
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
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>len>>n&&len)
	{
		for (int i=0;i>a[k];
		}
		computed(0,n+1);
		cout<<"The minimum cutting is "<

例题9-10

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

#include
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=0;i--)//起点从第n-2个字符开始 
		for (int j=i+1;jj) 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>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
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>x[i]>>y[i];
		//确定边界
		for (int i=0;i=0;i--)
			{
				d[i][j]=INF;
				double ans;
				for(int k=i+1;k0.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
using namespace std;
const int maxn=100000+10;
int N,T;
vector sons[maxn];
int dp(int u)
{
	if(sons[u].empty()) return 1;
	int k=sons[u].size();
	vector d;
	for(int i=0;i>N>>T&&T)
	{
		for(int i=0;i>j;
			sons[j].push_back(i);
		}
		cout<

例题9-13

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

#include
using namespace std;
const int maxn=200+10;
map n2id;
vector 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;id[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>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][0]) 
		{
			cout<


例题9-15

本题目中,采用位运算的与、或、异或来处理状态转化后的集合变化。代码采用的是代码仓库中的代码,我稍加了注释,更利于阅读。

//状态为d(i,s1,s2)表示考虑前i个人,
//只有1个人教的S1中的科目,至少两个人教S2种的科目的最小花费。
//状态转移方程为 d(i,s1,s2)=min{d(i+1,s1',s2')+c[i],d(i+1,s1,s2)},
//前面的状态表示招聘一人,s1',s2'表示招聘一人后s1和s2的新值。
// UVa10817 Headmaster's Headache
// Rujia Liu
#include
#include
#include
#include
using namespace std;

const int maxn = 100 + 20 + 5;
const int maxs = 8;
const int INF = 1000000000;
int m, n, s, c[maxn], st[maxn], d[maxn][1<= 0) return ans;
  ans = INF;//初值为INF
   // 不选
  if(i >= m) ans = dp(i+1, s0, s1, s2); 
  // 选,m0表示 i个人能够教没人教的课的集合
  //m1表示 i个人能够教一门的课的集合
  int m0 = st[i] & s0, m1 = st[i] & s1;
  s0 ^= m0; //更新s0 
  s1 = (s1 ^ m1) | m0;//更新s1, 
  s2 |= m1;
  ans = min(ans, c[i] + dp(i+1, s0, s1, s2));
  return ans;
}

int main() {
  int x;
  string line;
  while(getline(cin, line)) {
    stringstream ss(line);
    ss >> s >> m >> n;
    if(s == 0) break;

    for(int i = 0; i < m+n; i++) {
      getline(cin, line);
      stringstream ss(line);
      ss >> c[i];
      st[i] = 0;
      //通过|=的方式将所能教的课程加入集合 
      while(ss >> x) st[i] |= (1 << (x-1));
      //通过getline来读取后,这样可以判断一行的末尾就为结束 
    }
    memset(d, -1, sizeof(d));
    cout << dp(0, (1<

例题9-16

本题提供了代码仓库中源码,具体地址点击打开链接,自己并没有看懂。还需要学习。

// UVa1252 Twenty Questions
// Rujia Liu
#include
#include
#include
#include
#include
using namespace std;

const int maxn = 128;
const int maxm = 11;

int kase, n, m;
char objects[maxn][maxm + 100];

int vis[1<= 1 && cnt[s2][a] >= 1) {
        int need = max(dp(s2, a2),     // the object has feature k
                       dp(s2, a)) + 1; // the object doesn't have feature k
        ans = min(ans, need);
      }
    }
  return ans;
}

void init() {
  for(int s = 0; s < (1<




你可能感兴趣的:(算法竞赛)