算法竞赛入门经典(第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<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;
}


例题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<cstdio>
#include<cstring>
#include<iostream>
#include<sstream>
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<<maxs][1<<maxs];

// s0来表示没有任何人能教的科目 s1是一个人教的科目集合,s2是两个人教的科目集合
int dp(int i, int s0, int s1, int s2) {
//如果人数已经到达上限,两个人教的科目不符合要求则返回INF,
//如果符合要求返回0 
  if(i == m+n) return s2 == (1<<s) - 1 ? 0 : INF;
  int& ans = d[i][s1][s2];
  //记忆化搜索部分,如果已经计算过,则返回 
  if(ans >= 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<<s)-1, 0, 0) << "\n";
  }
  return 0;
}

例题9-16

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

// UVa1252 Twenty Questions
// Rujia Liu
#include<cstdio>
#include<ctime>
#include<cstring>
#include<algorithm>
#include<cassert>
using namespace std;

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

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

int vis[1<<maxm][1<<maxm], d[1<<maxm][1<<maxm];
int cnt[1<<maxm][1<<maxm];
// cnt[s][a]: how many object satisfies: Intersect(featureSet(i), s) = a
// s: the set of features we already asked
// a: subset of s that the object has
int dp(int s, int a) {
  if(cnt[s][a] <= 1) return 0;
  if(cnt[s][a] == 2) return 1;
  int& ans = d[s][a];
  if(vis[s][a] == kase) return ans;
  vis[s][a] = kase;
  ans = m;
  for(int k = 0; k < m; k++)
    if(!(s & (1<<k))) { // haven't asked
      int s2 = s|(1<<k), a2 = a|(1<<k);
      if(cnt[s2][a2] >= 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<<m); s++) {
    for(int a = s; a; a = (a-1)&s)
      cnt[s][a] = 0;
    cnt[s][0] = 0;
  }
  for(int i = 0; i < n; i++) {
    int features = 0;
    for(int f = 0; f < m; f++)
      if(objects[i][f] == '1') features |= (1<<f);
    for(int s = 0; s < (1<<m); s++)
      cnt[s][s & features]++;
  }
}


int main() {
  memset(vis, 0, sizeof(vis));
  while(scanf("%d%d", &m, &n) == 2 && n) {
    ++kase;
    for(int i = 0; i < n; i++) scanf("%s", objects[i]);
    init();
    printf("%d\n", dp(0, 0));
  }
  return 0;
}




  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
大学生参加学科竞赛有着诸多好处,不仅有助于个人综合素质的提升,还能为未来职业发展奠定良好基础。以下是一些分析: 首先,学科竞赛是提高专业知识和技能水平的有效途径。通过参与竞赛,学生不仅能够深入学习相关专业知识,还能够接触到最新的科研成果和技术发展趋势。这有助于拓展学生的学科视野,使其对专业领域有更深刻的理解。在竞赛过程中,学生通常需要解决实际问题,这锻炼了他们独立思考和解决问题的能力。 其次,学科竞赛培养了学生的团队合作精神。许多竞赛项目需要团队协作来完成,这促使学生学会有效地与他人合作、协调分工。在团队合作中,学生们能够学到如何有效沟通、共同制定目标和分工合作,这对于日后进入职场具有重要意义。 此外,学科竞赛是提高学生综合能力的一种途径。竞赛项目通常会涉及到理论知识、实际操作和创新思维等多个方面,要求参赛者具备全面的素质。在竞赛过程中,学生不仅需要展现自己的专业知识,还需要具备创新意识和解决问题的能力。这种全面的综合能力培养对于未来从事各类职业都具有积极作用。 此外,学科竞赛可以为学生提供展示自我、树立信心的机会。通过比赛的舞台,学生有机会展现自己在专业领域的优势,得到他人的认可和赞誉。这对于培养学生的自信心和自我价值感非常重要,有助于他们更加积极主动地投入学习和未来的职业生涯。 最后,学科竞赛对于个人职业发展具有积极的助推作用。在竞赛中脱颖而出的学生通常能够引起企业、研究机构等用人单位的关注。获得竞赛奖项不仅可以作为个人履历的亮点,还可以为进入理想的工作岗位提供有力的支持。
算法竞赛入门经典--训练指南,代码仓库,有四个本的代码仓库。 《算法竞赛入门经典——训练指南》代码仓库 例题代码 限于篇幅,书上并没有给出所有例题的代码,这里给出了所有例题的代码,并且改进了书上的一些代码。 第一章 32题 38份代码 第二章 28题 30份代码 第三章 22题 23份代码 第四章 19题 21份代码 第五章 34题 39份代码 第六章 24题 26份代码 共159题 177份代码 为了最大限度保证代码风格的一致性,所有例题代码均由刘汝佳C++语言编写。 所有代码均通过了UVa/La的测试,但不能保证程序是正确的(比如数据可能不够强),有疑问请致信rujia.liu@gmail.com,或在googlecode中提出: http://code.google.com/p/aoapc-book/ [最新更新] 2013-04-23 增加字符串中例题10(UVa11992 Fast Matrix Operations)的另一个本的程序,执行效率较低,但更具一般性,可读性也更好 2013-04-22 增加字符串部分“简易搜索引擎”代码,可提交到UVa10679 2013-04-13 修正Treap中优先级比较的bug(原来的代码实际上是在比较指针的大小!),加入纯名次树代码 2013-03-31 修正UVa1549标程的bug,即buf数组不够大。 增加线段树部分“动态范围最小值”的完整代码 2013-03-23 修正UVa10054标程的bug,即没有判断是否每个点的度数均为偶数。UVa数据已经更新 LA3401修正了代码和文字不一致的问题 UVa11270增加了答案缓存 2013-03-21 增加线段树部分中两个经典问题的完整代码:快速序列操作I和快速序列操作II 2013-02-28 补全所有159道例题的代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值