算法作业题解

2021/12/26 14:07:17

本文主要是介绍算法作业题解,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

文章目录

  • 第一次算法作业
    • Problem A. 思维之花-方程
    • Problem C. 课堂作业-7-1
  • 第二次算法作业
    • Problem A. 课堂作业-6-1
    • Problem B. 课堂作业-5-1
    • Problem C. 课堂作业-4-4
    • Problem D. Superprime
    • Problem E. 整数变换
  • 第三次算法作业
    • Problem A. 课堂作业-6-2
    • Problem B. 课堂作业-7-2
    • Problem C. 课堂作业-9-1
    • Problem D. 包裹快递
    • Problem E. 课堂作业-8-2
  • 第四次算法作业
    • Problem A. 采药
    • Problem B. 装箱问题
    • Problem C. 合唱队形
    • Problem D. 马拦过河卒
    • Problem E. 传球游戏
  • 第五次算法作业
    • Problem A. 单行最大子矩阵和问题
    • Problem B. 多行最大子矩阵和问题
    • Problem C. 小明的打工计划
    • Problem D. 邮票面值设计
    • Problem E. 导弹拦截
  • 第六次算法作业
    • Problem A. 合并果子
    • Problem B. 最小差距
    • Problem C. 上帝的爱好
    • Problem D. 任务调度问题
    • Problem E. sqy 的锡纸烫
  • 第七次算法作业
    • Problem A. 海战
    • Problem B. 课堂作业-9-2
    • Problem C. 课堂作业-9-4
    • Problem D. 课堂作业-8-4
    • Problem E. 24点游戏

第一次算法作业

Problem A. 思维之花-方程

时间限制 1000 ms
内存限制 128 MB

题目描述

有形如:ax^3+bx^2+cx+d=0 这样的一个一元三次方程。给出该方程中各项的系数(a,b,c,d 均为实数),并约定该方程存在三个不同实根(根的范围在-100至100之间),且根与根之差的绝对值> =1。要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后2位。
  提示:记方程f(x)=0,若存在2个数x1和x2,且x1< x2,f(x1)*(x2)< 0,则在(x1,x2)之间一定有一个根。

输入数据

输入该方程中各项的系数 (a,b,c,d (a,b,c,d 均为实数),

输出数据

由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后 22 位。

样例输入

1 -5 -4 20

样例输出

-2.00 2.00 5.00
#include <iostream>
#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iomanip>

using namespace std;
double a, b, c, d;
#define f(x) (a*(x)*(x)*(x)+b*(x)*(x)+c*(x)+d)

void ef(double s, double e)
{
	if (f(s) * f(e) <= 0)
	{
		double mid = (s + e) / 2.0;
		if (fabs(f(mid)) <= 1e-8)
		{
			cout<<setiosflags(ios::fixed) << setprecision(2) << mid << " ";
			return;
		}
		if (f(mid) * f(s) < 0)
			ef(s, mid);
		else
			ef(mid, e);
	}
}

int main()
{
	cin >> a >> b >> c >> d;
	for (double s = -101; s <= 101; s += 1)
	{
		ef(s, s + 1);
	}
	return 0;
}

Problem C. 课堂作业-7-1

时间限制 1000 ms
内存限制 64 MB

题目描述

有n根小木棒,任选三根木棒组成一个三角形,问三角形周长最大是多少。(保证至少存在一种选法能组成三角形)

输入数据

第一行为一个正整数n,3=<n<=100 第二行为n个正整数,代表小木棒长度,不超过100.

输出数据

三角形周长的最大值

样例输入

5
1 2 3 4 5

样例输出

12

解法1:

#include<iostream>
#include<algorithm>
using namespace std;
const int MAX_N = 105;

int cmp(const void* a, const void* b)
{
	return *(int*)a - *(int*)b;
}
void solve(int n, int* a)
{
	qsort(a, n, sizeof(a[0]), cmp);
	int sum = 0;
	for (int i = n - 1; i > 1; --i)
	{
		if (a[i] + a[i - 1] > a[i - 2])
			if (a[i] + a[i - 2] > a[i - 1])
				if (a[i - 1] + a[i - 2] > a[i])
				{
					sum = a[i] + a[i - 1] + a[i - 2]; break;
				}
	}
	cout << sum;
}

int main()
{
	int n, a[MAX_N];
	cin >> n;
	for (int i = 0; i < n; ++i)
		cin >> a[i];
	solve(n, a);
	return 0;
}

第二次算法作业

Problem A. 课堂作业-6-1

时间限制 1000 ms
内存限制 64 MB

题目描述

如果一个质数能被表示为三个不同的质数的和的形式,那么我们称它为立方质数。现在给你一个数n,判断它是不是立方质数。

输入数据

正整数n,n<=1000

输出数据

Yes或者No

样例输入

19

样例输出

Yes

解法1

#include <iostream>
#include <cmath>
using namespace std;
//判断是否为质数
bool isPrime(int num) {
    if (num <= 3) {
        return num > 1;
    }
    // 不在6的倍数两侧的一定不是质数
    if (num % 6 != 1 && num % 6 != 5) {
        return false;
    }
    
    for (int i = 5; i <= sqrt(num); i += 6) {
        if (num % i == 0 || num % (i + 2) == 0) {
            return false;
        }
    }
    return true;
}
int main() {
    int n;
    cin >> n;
    bool flag = false;
    if (!isPrime(n)) {
        cout << "No" << endl;
    }
    else {
        for (int i = 1; i <= n; i++) {
            if (isPrime(i)) {
                for (int j = i + 1; j <= n; j++) {
                    if (isPrime(j) && isPrime(n - i - j) && (n - i - j) != i && (n - i - j) != j) {
                        flag = true;
                    }
                }
            }
        }
        if (flag) cout << "Yes" << endl;
        else cout << "No" << endl;
    }
    return 0;
}

Problem B. 课堂作业-5-1

时间限制 1000 ms
内存限制 64 MB

题目描述

有n盏灯,编号都是1-n,初始灯都是关着的。有n个人,第1个人会打开所有的灯,第2个人会按下所有编号为2的倍数的灯的开关(这些灯将被关掉),第3个人会按下所有编号为3的倍数的开关,以此类推,第n个人会按下所有编号为n的倍数的开关。问最后有几盏灯是亮着的。

输入数据

一个正整数n,n<100000

输出数据

亮着的灯的数量

样例输入

5

样例输出

2

解法1:

#include <iostream>
using namespace std;

int check(int x) {
    int cnt = 0;
    int i;
    for (i = 1; i * i < x; ++i) {
        if (!(x % i)) {
            cnt += 2;
        }
    }
    if (i * i == x) {
        ++cnt;
    }
    return cnt;
}
int main() {
    int n;
    cin >> n;
    int cnt = 0;
    for (int i = 1; i <= n; ++i) {
        if (check(i) % 2) {
            ++cnt;
        } 
    }
    cout << cnt << endl;
    return 0;
}

解法2:

# include "iostream"
# include "vector"
# include "numeric"
using namespace std;
#define  MAX_SIZE 100000

int main(){
    int n;
    cin >>n;
    vector<int> p(n+1, 1);
    for(int i = 2; i <= n; i++){
        for(int j = 1; j*i <= n; j++){
            if(p[j*i] == 1)
                p[j*i] = 0;
            else
                p[j*i] = 1;
        }
    }
    p[0] = 0;
    cout << accumulate(p.begin(), p.end(), 0) << endl;  // 累加范围,和累加初值
    return 0;

}

解法3:

#include <iostream>
using namespace std;
int main(){
    int n;
    cin >> n;
    int i = 1;
    for(; i * i <= n; i++){}
    cout << i - 1 << endl;
    return 0;
}

Problem C. 课堂作业-4-4

时间限制 1000 ms
内存限制 64 MB

题目描述

小明刚买了一个机械键盘,但他在用机械键盘打字的时候发现键盘的Home键和End键坏掉了,于是他在打字的时候光标有时会突然跑到行首,有时又会突然跑到行尾,现在给你键盘的输入,求最后屏幕上的字符串。

输入数据

输入数据为一行字符串,字符串只包含小写英文字母,’[‘符号以及’]‘符号,’['代表按下了Home键,‘]’代表按下了End键,字符串长度不超过100000。

输出数据

输出最后屏幕上的字符串。

样例输入

xiechengxuz[henhao]wan

样例输出

henhaoxiechengxuzwan

样例说明

可能出现多个’[‘和’]’,也可能没有’[‘和’]’

解法1:

# include <iostream>
# include <string>
# include <queue>
using namespace std;
deque<string> q;
string buf;
void fun(int flag, string& buf) {
	if (flag)
		q.push_back(buf);
	else
		q.push_front(buf);
	buf.clear();
}
int main() {
	string s;
	cin >> s;
	int flag = 0;// flag=0,放在前面; flag =1, 放在后面
	for (int i = 0; i < s.length(); i++)
	{
		if (s[i] != '[' && s[i] != ']') {
			buf += s[i];
		}else if (s[i] == '[') {
			fun(flag, buf);
			flag = 0;
		}
		else {
			fun(flag, buf);
			flag = 1;
		}	
	}
	fun(flag, buf);
	for (auto i : q)
		cout << i;
}

Problem D. Superprime

时间限制 1000 ms
内存限制 128 MB

题目描述

农民约翰的母牛总是生产出最好的肋骨。你能通过农民约翰和美国农业部标记在每根肋骨上的数字认出它们。
农民约翰确定他卖给买方的是真正的质数肋骨,是因为从右边开始切下肋骨,每次还剩下的肋骨上的数字都组成一个质数,举例来说:
7 3 3 1
全部肋骨上的数字 7331是质数;三根肋骨 733是质数;二根肋骨 73 是质数;当然,最后一根肋骨 7 也是质数。

7331 被叫做长度 4 的特殊质数。
写一个程序对给定的肋骨的数目 N (1< =N< =8),求出所有的特殊质数。数字1不被看作一个质数。

输入数据

单独的一行包含 N 。

输出数据

按顺序输出长度为 N 的特殊质数,每行一个。
并按大小顺序排列(从小到大).

样例输入

4

样例输出

2333
2339
2393
2399
2939
3119
3137
3733
3739
3793
3797
5939
7193
7331
7333
7393

解法1:

#include<iostream>
#include<cmath>
#include <queue>
using namespace std;
bool isPrime(int num) { //判断是否为质数
	if (num <= 3) {
		return num > 1;
	}
	if (num % 6 != 1 && num % 6 != 5) { // 不在6的倍数两侧的一定不是质数
		return false;
	}
	for (int i = 5; i <= sqrt(num); i += 6) {
		if (num % i == 0 || num % (i + 2) == 0) {
			return false;
		}
	}
	return true;
}


int main() {
	queue <int> q;
	int n, m = 4;
	int  a[] = { 2,3,5,7 };   // 单独一个数是质数
	int b[] = { 1,3,7,9 };    //一这些数字为结尾的,可能是质数
	cin >> n;
	for (int i = 0; i < 4; i++) q.push(a[i]);
	for (int i = 2; i <= n; i++) {
		int l = m;
		m = 0;
		for (int j = 0; j < l; j++) {
			for (int k = 0; k < 4; k++)  //选择枚举b[k]
				if (isPrime(q.front() * 10 + b[k]))    
					q.push(q.front() * 10 + b[k]), m++;
			q.pop();
		}
	}
	while (!q.empty()) {
		cout << q.front() << endl;
		q.pop();
	}
	return 0;
}

解法2

#include <iostream>
#include <vector>
using namespace std;

int mn = 1, mx = 1;
vector<int> bones;

bool isprime(int x) {
    if (x == 2) {
        return true;
    }
    if (x == 1 || !(x % 2)) {
        return false;
    }
    for (int i = 3; i * i <= x; i += 2) {
        if (!(x % i)) {
            return false;
        }
    } 
    return true;
}

void dfs(int x) {
    if (x >= mn && x < mx) {
        bones.push_back(x);
        return;
    }
    x *= 10;
    for(int i = 1; i < 10; ++i) {
        if (isprime(x + i)) {
            dfs(x + i);
        }
    }
}

int main() {
    int n;
    cin >> n;
    while (--n) {
        mn *= 10;
        mx = mn;
    }
    mx *= 10;
    dfs(0);
    for (int i : bones) {
        cout << i << endl;
    }
    return 0;
}

Problem E. 整数变换

时间限制 1000 ms
内存限制 128 MB

题目描述

给出一个小于2^32的正整数。这个数可以用一个32位的二进制数表示(不足32位用0补足)。我们称这个二进制数的前16位为“高位”,后16位为“低位”。将它的高低位交换,我们可以得到一个新的数。试问这个新的数是多少(用十进制表示)。
  例如,数1314520用二进制表示为0000 0000 0001 0100 0000 1110 1101 1000(添加了11个前导0补足为32位),其中前16位为高位,即0000 0000 0001 0100;后16位为低位,即0000 1110 1101 1000。将它的高低位进行交换,我们得到了一个新的二进制数0000 1110 1101 1000 0000 0000 0001 0100。它即是十进制的249036820。

输入数据

一个小于 232232 的正整数

输出数据

将新的数输出

样例输入

1314520

样例输出

249036820

解法1:

#include<iostream>
using namespace std;
int main()
{
    unsigned long long x;
    cin >> x;
    cout << ((x & 0x0000ffff) << 16 | (x & 0xffff0000) >> 16) << endl;
}

第三次算法作业

Problem A. 课堂作业-6-2

时间限制 1000 ms
内存限制 64 MB

题目描述

我们有n根的木棍。现在从这些木棍中切割出来m条长度相同的木棍,问这m根木棍最长有多长?

输入数据

第一行输入两个数字,n(1<=n<=1000)为木棍数目,m(1<=m<=1000)为需要切割出的相同长度的木棍数目 随后n个正整数,表示原始木棍的长度(<=10000)

输出数据

每组输出一行结果,表示切割后绳子的最长长度(保留两位小数)

样例输入

4 5
5 6 7 8

样例输出

4.00

解法1

#include <iostream>
using namespace std;
#include <vector>
#include <iomanip>
#include <cmath>
int main()
{
	int n,m;
	cin >>n>> m;
	vector<int> v;
	double max=0;
	for (int i = 0; i < n; i++) {
		int t;
		cin >> t;
		v.push_back(t);
		if (t > max)
			max = t;
	}
	double s = 0, e = max;
	double mid;
	while (s + 0.01 < e) {
		int cnt = 0;
		mid = round(((s + e) / 2.00) * 100) / 100.00;  //四舍五入
		for (int i = 0; i < n; i++)
			cnt += (double)(v[i] / mid);
		if (cnt >= m) s = mid;
		else e = mid;
	}
	cout << setiosflags(ios::fixed) << setprecision(2) << s ; 
}

Problem B. 课堂作业-7-2

时间限制 1000 ms
内存限制 64 MB

题目描述

有一条河,河中间有一些石头,已知石头的数量和相邻两块石头之间的距离。现在可以移除一些石头,问最多移除m块石头后(首尾两块石头不可以移除),相邻两块石头之间的距离的最小值最大是多少。

输入数据

第一行输入两个数字,n(2<=n<=1000)为石头的个数,m(0<=m<=n-2)为可移除的石头数目 随后n-1个数字,表示顺序和相邻两块石头的距离d(d<=1000)

输出数据

输出最小距离的最大值

样例输入

4 1
1 2 3

样例输出

3

解法1:

#include <stdio.h>
int n, m,ans;
int d[1005];
int l[1005] = { 0 };
int main() {
	scanf("%d%d", &n, &m);
	for (int i = 2; i <= n; i++) {
		scanf("%d", &d[i]);
		l[i] = d[i] + l[i - 1];
	}
	int left=0,right=l[n];
	while(left<=right)
	{
		int now=0,mid=(left+right)>>1,s=0;
		for(int i=2;i<=n;++i)
		{
			if(l[i]-l[now]<mid) ++s;
			else now=i;
		}
		if(s>m) right=mid-1;
		else 
		{
			ans=mid;
			left=mid+1;
		}
	}
	printf("%d",ans);
	return 0;
}

Problem C. 课堂作业-9-1

时间限制 1000 ms
内存限制 64 MB

题目描述

楼梯有n阶,可以一步上一阶、两阶或三阶,问有多少种不同的走法
由于答案很大,mod(1e9+7)输出

输入数据

一个正整数n,代表楼梯的阶数,n<=1000000

输出数据

方案数

样例输入

3

样例输出

4

解法1

# include <iostream>
# include <cmath>
# include <vector>
using namespace std;

int main() {
	long  n;
	cin >> n;
	long long f[3];
	f[0] = 1; 
	f[1] = 2;
	f[2] = 4;
	int mod = (1e9 + 7);
	long long ans;
	if (n < 3) {
		ans = f[n - 1];
	}
	else {
		for (long i = 3; i < n; i++)
		{
			long long t;
			t = f[0];
			f[0] = f[1];
			f[1] = f[2];
			f[2] = (t + f[0] + f[1]) % mod;
		}
		ans = f[2];
	}
	cout << ans;
}

Problem D. 包裹快递

时间限制 1000 ms
内存限制 128 MB

题目描述

一个快递公司要将n个包裹分别送到n个地方,并分配给邮递员小K一个事先设定好的路线,小K需要开车按照路线给的地点顺序相继送达,且不能遗漏一个地点。小K得到每个地方可以签收的时间段,并且也知道路线中一个地方到下一个地方的距离。若到达某一个地方的时间早于可以签收的时间段,则必须在这个地方停留至可以签收,但不能晚于签收的时间段,可以认为签收的过程是瞬间完成的。
  为了节省燃料,小K希望在全部送达的情况下,车的最大速度越小越好,就找到了你给他设计一种方案,并求出车的最大速度最小是多少。

输入数据

第 11 行为一个正整数 n (n<2×104),n (n<2×104), 表示需要运送包裹的地点数。
  下面 nn 行,第 i+1i+1 行有 33 个正整数 xi,yi,si,xi,yi,si, 表示按路线顺序给出第 ii 个地点签收包裹的时间段为 [xi,yi],[xi,yi], 即最早为距出发时刻 xi,xi, 最晚为距出发时刻 yi,yi, 从前一个地点到达第 ii 个地点距离为 si,si, 且保证路线中 xixi 递增。
  可以认为 s1s1 为出发的地方到第 11 个地点的距离,且出发时刻为 00 。

输出数据

仅包括一个整数,为车的最大速度最小值,结果保留两位小数。

样例输入

3
1 2 2
6 6 2
7 8 4

样例输出

2.00

样例说明

第一段用1的速度在时间2到达第1个地点,第二段用0.5的速度在时间6到达第2个地点,第三段用2的速度在时间8到达第3个地点。

解法1:

#include <stdio.h>
#define maxn 200005
long double st=0,en=1e9,mid;
int i,n;
int v[maxn][3];

bool check(long double x) {
	long double  minT, totalT=0;
	for (int i = 0; i < n; i++) {
		minT = v[i][2] / x;
		totalT = minT + totalT;
		if (totalT < v[i][0])
			totalT = v[i][0];
		else if (totalT > v[i][1])
			return  false;
	}
	return true;
}
int main(){
    scanf("%d",&n);
	for(i=0;i<n;i++)scanf("%d%d%d",&v[i][0], &v[i][1], &v[i][2]); 
    while(en-st>1e-9){
        mid=(en+st)/2.00;
        if(check(mid)) en=mid;
        else st=mid;
		}
    printf("%.2f",double(st));
    return 0;
}

Problem E. 课堂作业-8-2

时间限制 1000 ms
内存限制 64 MB

题目描述

有ABC三根杆和一些圆盘,开始的时候圆盘从小到大摞在A杆上,小盘在上大盘在下,规定如果圆盘p摞在圆盘q上面,那么rp<=rq,rp和rq为p和q的半径。
现在有若干个圆盘,半径从1到n,半径为i的圆盘有i个,每次操作可以移动一个圆盘,问把所有圆盘从A杆移动到C杆上最少需要几次操作。 由于最终答案可能很大,所以答案对1e9+7取模输出。

输入数据

一个正整数n,n<=1e5

输出数据

最少操作次数

样例输入

2

样例输出

4

样例说明

第一步把半径为1的圆盘从A放到B 第二步和第三步把两个半径为2的圆盘从A放到C 第四步把半径为1的圆盘从B放到C

解法1:

#include <iostream>
using namespace std;

int main() {
	int n;
	cin >> n;
	long long cnt=0;
	long long mod = 1e9 + 7;
	for (long long  i = 1; i <= n; i++)
	{
		cnt =(cnt*2+i)%mod;
	}
	cout << cnt<<endl;
}

第四次算法作业

Problem A. 采药

时间限制 1000 ms
内存限制 128 MB

题目描述

辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
  如果你是辰辰,你能完成这个任务吗?

输入数据

输入的第一行有两个整数 T (1≤T≤1000) 和 M (1≤M≤100) 用一个空格隔开,T 代表总共能够用来采药的时间 ,M 代表山洞里的草药的数目。接下来的 M 行每行包括两个在 1 到100之间(包括 1 和100)的整数,分别表示采摘某株草药的时间和这株草药的价值。

输出数据

输出包括一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。

样例输入

70 3
71 100
69 1
1 2

样例输出

3

解法1

#include <iostream>
#include <cstring>
using namespace std;
const int maxn = 1e4;
int f[maxn]; //f:草药的总价值
int w[maxn], val[maxn]; //w: 时间花费  val:草药价值
void solve(int m, int t) {
	memset(f, 0, sizeof f);
	for (int i = 1; i <= m; i++) {
		for (int v = t; v > 0; v--) {
			if (v >= w[i])
				f[v] = max(f[v], f[v - w[i]] + val[i]);
		}
	}
	cout << f[t];
}
int main() {
	int m, t; //t 采药的时间,m草药的数目
	cin >> t >> m;
	for (int i = 1; i <= m; i++) {
		cin>>w[i] >> val[i] ;
	}
	solve(m,t);
	return 0;
}

Problem B. 装箱问题

时间限制 1000 ms
内存限制 128 MB

题目描述

有一个箱子容量为v(正整数,o≤v≤20000),同时有n个物品(o≤n≤30),每个物品有一个体积 (正整数)。要求从 n 个物品中,任取若千个装入箱内,使箱子的剩余空间为最小。

输入数据

第一行,一个整数,表示箱子容量;
第二行,一个整数,表示有 n 个物品;
接下来 n 行,分别表示这 n 个物品的各自体积。

输出数据

一个整数,表示箱子剩余空间。

样例输入

24
6
8
3
12
7
9
7

样例输出

0

解法1

# include <iostream>
# include <algorithm>
# include <cmath>
using namespace std;
	int v;
	int n;
	int a[35];
	int dp[20005];
int main() {
	cin >> v >> n;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
	}
    for(int i=1;i<=n;i++)
        for(int j=v;j>=a[i];j--)
            dp[j]=max(dp[j],dp[j-a[i]]+a[i]);
	int minV = v - dp[v];
	cout << minV;
	return 0; 
}

Problem C. 合唱队形

时间限制 1000 ms
内存限制 128 MB

题目描述

​ N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。
  合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK, 则他们的身高满足T1< …< Ti> Ti+1> …> TK(1< =i< =K)。
  你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

输入数据

​ 输入的第一行是一个整数 N (2≤N≤100), 表示同学的总数。第一行有 n 个整数,用空格分隔,第 i 个整数 Ti (130≤Ti≤230) 是第 i 位同学的身高(厘米)。

输出数据

​ 输出包括一行,这一行只包含一个整数,就是最少需要几位同学出列。

样例输入

8
186 186 150 200 160 130 197 220

样例输出

4

解法一

# include <iostream>
using namespace std;
int l[105],r[105],h[105],dp[105];
int main() {
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>h[i];
		l[i]=r[i]=1;
	}
	for(int i=n-1;i>=1;i--){
		for (int j=i+1;j<=n;j++)
		{
			if(h[i]>h[j]&&r[i]<=r[j]+1)
				r[i]=r[j]+1;	
		} 
	}
	for(int i=2;i<=n;i++){
		for(int j=1;j<i;j++){
			if(h[i]>h[j]&&l[i]<=l[j]+1)
				l[i]=l[j]+1;
		}
	}
	int max=0;
	for(int i=1;i<=n;i++){
		dp[i]=l[i]+r[i]-1;
		if(dp[i]>max)
			max=dp[i];
	}
	cout<<n-max;
}

Problem D. 马拦过河卒

时间限制 1000 ms
内存限制 128 MB

题目描述

棋盘上A点有一个过河卒,需要走到目标B点。卒行走的规则:可以向下、或者向右。同时在棋盘上C点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。
  棋盘用坐标表示,A点(0, 0)、B点(n, m)(n, m为不超过15的整数),同样马的位置坐标是需要给出的。现在要求你计算出卒从A点能够到达B点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。

输入数据

一行四个数据,分别表示 B 点坐标和马的坐标。

输出数据

一个数据,表示所有的路径条数。

样例输入

6 6 3 3

样例输出

6

解法1

#include<iostream>
#define ll long long
using namespace std;
bool a[30][30];
ll dp[30][30];
//B(n,m) ma(x,y)
int main() {
	int n, m, x, y;
	cin >> n >> m >> x >> y;
	dp[1][1] = 1;
	n++; m++; x++; y++;
	a[x][y] = 1;
	a[x - 1][y + 2] = 1;
	a[x - 1][y - 2] = 1;
	a[x + 1][y + 2] = 1;
	a[x + 1][y - 2] = 1;
	a[x - 2][y - 1] = 1;
	a[x - 2][y + 1] = 1;
	a[x + 2][y - 1] = 1;
	a[x + 2][y + 1] = 1;
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++) {
			if ((i != 1 || j != 1) && !a[i][j])
				dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
		}
	}
	cout << dp[n][m];
}

Problem E. 传球游戏

时间限制 1000 ms
内存限制 128 MB

题目描述

上体育课的时候,小蛮的老师经常带着同学们一起做游戏。这次,老师带着同学们一起做传球游戏。
  游戏规则是这样的:n个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球,每个同学可以把球传给自己左右的两个同学中的一个(左右任意),当老师再次吹哨子时,传球停止,此时,拿着球没传出去的那个同学就是败者,要给大家表演一个节目。
  聪明的小蛮提出了一个有趣的问题:有多少种不同的传球方法可以使得从小蛮手里开始传的球,传了m次以后,又回到小蛮手里。两种传球方法被视作不同的方法,当且仅当这两种方法中,接到球的同学按接球顺序组成的序列是不同的。比如有三个同学1号、2号、3号,并假设小蛮为1号,球传了三次回到小蛮手里的方式有1-> 2-> 3-> 1和1-> 3-> 2-> 1,共2种。

输入数据

输入共一行,有两个用空格隔开的整数 n,m (3≤n≤30,1≤m≤30)。

输出数据

输出共一行,有一个整数,标示符合题意的方法数。

样例输入

3 3

样例输出

2

样例说明

40%的数据满足:3< =n< =30 1< =m< =20
100%的数据满足:3< =n< =30 1< =m< =30

解法1:

dp[i][j]: 传了i次球,传到编号为j的同学的方法数。

#include<iostream>
using namespace std;
int dp[40][40];
int main() {
	int n, m, x, y;
	cin >> n >> m;
	dp[0][1] = 1;
	for (int i = 1; i <= m; i++) {
		for (int j = 1; j <= n; j++) {
			x = j - 1;
			y = j + 1;
			if (x == 0) x = n;
			if (y == n + 1) y = 1;
			dp[i][j] = dp[i - 1][x] + dp[i - 1][y];
		}
	}
	cout << dp[m][1];
}

第五次算法作业

Problem A. 单行最大子矩阵和问题

时间限制 1000 ms
内存限制 64 MB

题目描述

给定1×N的单行矩阵,矩阵每个元素都是-127到+127之间的整数。请找到一个连续子矩阵,使得其元素之和最大。例如行矩阵0 -2 -7 0 -2 11 -4 13 -5 -2,最大子矩阵和为11+(-4)+13=20.

输入数据

多组测试数据,每组数据的第一行为一个整数 N (N<=100),第二行包含N个整数,为行矩阵的N个元素,每个元素介于-127到127之间。

输出数据

最大子矩阵之和,每组对应一行。

样例输入

10
0 -2 -7 0 -2 11 -4 13 -5 -2

样例输出

20

解法1

#include <stdio.h>
int N;
int a[105];

int main(){
	while(scanf("%d",&N)!=EOF){
		for(int i=0;i<N;i++ ){
			scanf("%d",&a[i]);			
		}
		int sum=0;
		int max=a[0];
		for(int i=0;i<N;i++){
			sum+=a[i];
			if(sum<0)
				sum=0;
			if(sum>max)
				max=sum;
		}
		printf("%d\n",max);
	}
} 

Problem B. 多行最大子矩阵和问题

时间限制 1000 ms
内存限制 64 MB

题目描述

给定N×N矩阵,矩阵元素都是-127到+127之间的整数。请找到一个子矩阵,使得其元素之和最大。例如给定4*4矩阵 0 -2 -7 0 9 2 -6 2 -4 1 -4 1 -1 8 0 -2 最大子矩阵为 9 2 -4 1 -1 8 最大子矩阵和为9+2+(-4)+1+(-1)+8 = 15.

输入数据

多组测试数据,每组测试数据的第一行整数 N (N<=100)。接下来N行元素,每行N个元素,每个元素介于-127到127之间。

输出数据

最大子矩阵元素之和,每组测试数据对应一行。

样例输入

4
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2

样例输出

15

解法1

#include <stdio.h>
#include <string.h>
int n;
int a[105][105];
int temp[105];
int main(){
	while(scanf("%d",&n)!=EOF){
		for(int i=0;i<n;i++ ){
			for(int j=0;j<n;j++){
				scanf("%d",&a[i][j]);	
			}	
		}
		int max=-32767;
		for(int i=0;i<n;i++){
			memset(temp,0,sizeof(temp));
			for(int j=i;j<n;j++){
				int sum=0; int tempmax=-32767;
				for(int k=0;k<n;k++){
					temp[k]+=a[j][k];
					sum+=temp[k];
					if(sum<0) sum=0;
					if(sum>tempmax) tempmax=sum;
				}
				if(tempmax>max) max=tempmax;
			}
		}
		printf("%d\n",max);
	}
} 

Problem C. 小明的打工计划

时间限制 2000 ms
内存限制 64 MB

题目描述

作为一个有很多游戏想买但囊中羞涩的大学生,小明决定在这个暑假开始打工赚钱。经过一段时间的寻找,他一共找到了n个打工的招聘广告,其中第i个打工的日期从li开始,到ri为止,一共付给他ci元钱。因为这些打工的时间都相互冲突,所以同一天小明最多参加一个打工,并且一个打工一旦开始,就必须一直工作到结束,不能中途退出。现在小明想要知道,这个暑假他打工最多能得到多少钱?

输入数据

第一行一个整数n(1≤n≤10000001≤n≤1000000),表示招聘广告的数量。 接下来一共n行,每行3个整数li,ri,ci(1≤li≤ri≤1000000,1≤ci≤10000000001≤li≤ri≤1000000,1≤ci≤1000000000),表示打工的开始时间,结束时间和报酬。

输出数据

一行一个整数k,表示小明最多能够得到的钱数。

样例输入

3
1 2 3
3 4 3
2 3 5

样例输出

6

解法1

#include <iostream>
#include <algorithm>
#include <math.h>
using namespace std;
struct job{
	int l,r,w;
}a[1100000]; 
long long f[1100000];
int n,id;

bool cmp(job x,job y){
	return (x.r<y.r )||(x.r==y.r && x.l<y.l);
}

int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i].l>>a[i].r>>a[i].w;
	}
	sort(a+1,a+n+1,cmp);
	id=1;
	for(int i=1;i<=a[n].r;i++){
		f[i]=f[i-1];
		while(i==a[id].r&&id<=n){ // 可能有多个项目的结束时间一致
			f[i]=max(f[i],f[a[id].l-1]+a[id].w);
			id++;	
		}		
	}
	cout<<f[a[n].r];
}

Problem D. 邮票面值设计

时间限制 1000 ms
内存限制 128 MB

题目描述

给定一个信封,最多只允许粘贴N张邮票,计算在给定M(N+M< =10)种邮票的情况下(假定所有的邮票数量都足够),如何设计邮票的面值,能得到最大max ,使得1~max之间的每一个邮资值都能得到。
例如,N=3,M=2,如果面值分别为1分、4分,则在l分~6分之间的每一个邮资值都能得到(当然还有8分、9分和12分):如果面值分别为1分、3分,则在1分~7分之间的每一个邮资值都能得到。可以验证当N=3,M=2时,7分就是可以得到连续的邮资最大值,所以MAX=7,面值分别为l分、3分。
样例输入:共一行,两个整数,分表为N与M的值。

输入数据

一行,分别为 N,M。

输出数据

两行。
第一行为 mm 种邮票的面值,按升序排列,各数之间用一个空格隔开。
第二行为最大值。

样例输入

3 2

样例输出

1 3
max=7

题解1

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
int m,n,ans,a[1005],b[1005],s[1005];
int stamp(int t)
{
	int i,res;
	for (i=1; i<=1000; i++) b[i]=1e6;
	for (i=1; i<=t; i++) b[a[i]]=1;
	res=0;
	do
	{
		res++;
		for (i=1; i<=t; i++)
			if (res>a[i] && b[res-a[i]]+1<b[res])
				b[res]=b[res-a[i]]+1;
	}while (b[res]<=m);
	return res;
}
void find(int i)
{
	int k,z;
	z=stamp(i-1);
	if (i>n)
	{
        int ii;
		if (z-1>ans)
        {
            ans=z-1;
            for (ii=2; ii<=n; ii++) s[ii]=a[ii];
        }

		return;
	}
	for (k=z; k>=a[i-1]+1; k--)
	{
		a[i]=k;
		find(i+1);	
	}
}

int main()
{
    int i;
	cin>>m>>n;
	a[1]=1;
	find(2);
    s[1]=1;
	for (i=1; i<n; i++) 
		cout<<s[i]<<" ";
	cout<<s[i];
	cout<<endl;
	cout << "MAX=" << ans<< endl;
	return 0;
}

Problem E. 导弹拦截

时间限制 1000 ms
内存限制 128 MB

题目描述

某国为了防御敌国的导弹袭击,研发出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试验阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

输入数据

输入数据只有一行,该行包含若干个数据,之间用半角逗号隔开,表示导弹依次飞来的高度(导弹最多有 20枚,其高度为不大于 3×103 的正整数)。

输出数据

输出数据只有一行,该行包含两个数据,之间用半角逗号隔开。第一个数据表示这套系统最多能拦截的导弹数;第二个数据表示若要拦截所有导弹至少要再添加多少套这样的系统。

样例输入

389,207,155,300,299,170,158,65

样例输出

6,1

解法1

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int a[N], d1[N], d2[N], n;
int main() {
	char ch;
	int n=0;
	while (1){
		cin >> a[++n];
		ch= getchar();
		if(ch=='\n') break; 
	};
	int len1 = 1, len2 = 1;		//初始长度为1
	d1[1] = a[1];		//用于求不上升序列长度
	d2[1] = a[1];		//用于求上升序列长度
	for (int i=2; i<=n; i++) {		//从a[2]开始枚举每个数(a[1]已经加进去了)
		if (d1[len1] >= a[i]) d1[++len1] = a[i];		//如果满足要求(不上升)就加入d1
		else {		//否则用a[i]替换d1中的一个数
			int p1 = upper_bound(d1 + 1, d1 + 1 + len1, a[i], greater<int>()) - d1;
			d1[p1] = a[i]; 
		}
		if (d2[len2] < a[i]) d2[++len2] = a[i];		//同上
		else {
			int p2 = lower_bound(d2 + 1, d2 + 1 + len2, a[i]) - d2;
			d2[p2] = a[i];
		}
	}
	cout << len1 <<"," << len2-1;		//输出
	return 0;		//结束
}

第六次算法作业

Problem A. 合并果子

时间限制 1000 ms
内存限制 128 MB

题目描述

在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。
  每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。
  因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。
  例如有3种果子,数目依次为1,2,9。可以先将1、2堆合并,新堆数目为3,耗费体力为3。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为12,耗费体力为12。所以多多总共耗费体力=3+12=15。可以证明15为最小的体力耗费值。

输入数据

输入包括两行,第一行是一个整数 n (1<=n<103),n (1<=n<103), 表示果子的种类数。第二行包含 n 个整数,用空格分隔,第 i个整数 ai (1<=ai<2×103) 是第 ii 种果子的数目。

输出数据

输出包括一行,这一行只包含一个整数,也就是最小的体力耗费值。输入数据保证这个值小于 231。

样例输入

3 
1 2 9

样例输出

15 

解法1

#include<bits/stdc++.h>
using namespace std;
#define maxn 10005
int n;
int a[maxn];
int main(){
    ios::sync_with_stdio(false);cin.tie(0);

    cin>>n;
    priority_queue<int, vector<int>, greater<int>> que;
    for(int i = 0; i<n; i++){
        cin>>a[i];
        que.push(a[i]);
    }
    if(n==1){
        cout<<0;
        return 0;
    } 
    int ans = 0;
    while(!que.empty()){
        int t1 = que.top(); que.pop(); 
        int t2 = que.top(); que.pop();
        ans+=t1+t2;
        if(que.empty())break;
        que.push(t1+t2);
    }
    cout<<ans;

    return 0;
}

Problem B. 最小差距

时间限制 1000 ms
内存限制 128 MB

题目描述

给定一些不同的一位数字,你可以从这些数字中选择若干个,并将它们按一定顺序排列,组成一个整数,把剩下的数字按一定顺序排列,组成另一个整数。组成的整数不能以0开头(除非这个整数只有1位)。
  例如,给定6个数字,0,1,2,4,6,7,你可以用它们组成一对数10和2467,当然,还可以组成其他的很多对数,比如210和764,204和176。这些对数中两个数差的绝对值最小的是204和176,为28。
  给定N个不同的0~9之间的数字,请你求出用这些数字组成的每对数中,差的绝对值最小的一对(或多对)数的绝对值是多少?

输入数据

第一行包括一个数 T (T≤1000), 为测试数据的组数。
  每组数据包括两行,第一行为一个数 N (2≤N≤10), 表示数字的个数。下面一行为 N 个不同的一位数字。

输出数据

T 行,每行一个数,表示第 i 个数据的答案。即最小的差的绝对值。

样例输入

2
6
0 1 2 4 6 7
4
1 6 3 4

样例输出

28
5

解法1

#include<bits/stdc++.h>
using namespace std;

int n;
int a[12];
int vis[12];
void solve(){
    cin>>n;
    for(int i = 1; i<=n; i++) cin>>a[i];
    sort(a+1, a+n+1);   
    if(n==2){
        cout<<a[2]-a[1]<<'\n';
        return;
    } 
    
    if(n&1){    
        if(a[1]==0) swap(a[1], a[2]);
        int x = 0;
        for(int i = 1; i<=n/2+1; i++) x = 10*x+a[i];
        int y = 0;
        for(int i = n; i>=n/2+2; i--) y = 10*y+a[i];
        cout<<abs(x-y)<<'\n';
        return;
    }

    int ret = 1e9;
    for(int i = 2; i<=n; i++) {
        if(a[i-1]==0) continue;
        memset(vis, 0, sizeof(vis));
        int x = a[i], y = a[i-1];
        int l = 1, r = n;
        vis[i] = vis[i-1] = 1;
        for(int j = 1; j<=n/2-1; j++){
            while(vis[l]) l++;
            while(vis[r]) r--;
            x = 10*x+a[l];
            y = 10*y+a[r];
            vis[l] = vis[r] = 1;
            l++, r--;
        }
        ret = min(ret, abs(x-y));
    }
    cout<<ret<<'\n';
}

int main(){
    ios::sync_with_stdio(false); cin.tie(0);

    int tt; cin>>tt;
    while(tt--){
        solve();
    }
    return 0;
}

Problem C. 上帝的爱好

时间限制 1000 ms
内存限制 128 MB

题目描述

我们知道,词都是按照词牌来填的,上帝为了考验小杉,只给了他四种词牌,但只要压韵就算符合词牌。
小杉已经想好了N个意境优美的句子,每个句子都有一个韵脚。
符合要求的词的句式应当有如下四种" XXYY" ," XYXY" ," XYYX" ," XXXX" ,其中X或Y表示韵脚。
现在小杉想知道,从他想的N个句子之中,最多能按顺序挑选出几首符合条件的词。
并且词的句子间不能交错,比如你选了1 4 6 8做为一首诗,那么7你就不能再选了。

输入数据

每组测试数据的
第一行有一个数 N (N≤4000)。
第二行有N个不超10^{3}的正整数,第i个整数表示第i个句子的韵脚,整数相同表示韵脚相同。
3030 的数据 N≤100N≤100

输出数据

对每组测试数据输出一行,仅有一个数字,表示小杉最多能挑出几首词来。

样例输入

12
1 2 4 2 3 1 2 2 1 1 2 2

样例输出

2

样例说明

样例最多可以挑出两首词,一种方案如下:
1 2 4 6/9 10 11 12

解法1

#include <iostream>
using namespace std;
int a[10000];
int main(){
	int n;
	cin>>n;
	for(int i=0;i<n;i++)
		cin>>a[i];
		
	int s=0;
	int cnt=0;
	int j;
	int ans=0;
	for(int i=0;i<n;i++){
		for(j=s;j<i;j++){
			if(a[i]==a[j]){
				cnt++;
				a[i]=a[j]=0; //标记已用 
				break;
			}
		}
		if(cnt==2){ //两对相同的数 
			cnt =0;
			s=i;
			ans++;
		}
	}
	cout<<ans;
	return 0;
}

Problem D. 任务调度问题

时间限制 1000 ms
内存限制 128 MB

题目描述

一个单位时间任务是恰好需要一个单位时间完成的任务。给定一个单位时间任务的有限集S。关于S 的一个时间表用于描述S 中单位时间任务的执行次序。时间表中第1 个任务从时间0 开始执行直至时间1 结束,第2 个任务从时间1 开始执行至时间2 结束,…,第n个任务从时间n-1 开始执行直至时间n结束。具有截止时间和误时惩罚的单位时间任务时间表问题可描述如下:
(1) n 个单位时间任务的集合S={1,2,…,n}(n≤500)
(2) 任务i的截止时间d[i], 1≤i≤n, 1≤d[i]≤n,即要求任务i在时间d[i]之前结束;
(3) 任务i 的误时惩罚1≤w[i]< 1000,1≤i≤n,即任务i 未在时间d[i]之前结束将招致w[i]的惩罚;若按时完成则无惩罚。
任务时间表问题要求确定S 的一个时间表(最优时间表)使得总误时惩罚达到最小。

输入数据

第一行是正整数 n, 表示任务数。接下来的 2 行中,每行有 n个正整数,分别表示各任务的截止时间和误时惩罚。

输出数据

将计算出的最小总误时惩罚输出

样例输入

7
4 2 4 3 1 4 6
70 60 50 40 30 20 10

样例输出

50

解法1

#include <iostream>
#include <algorithm>
using namespace std;
struct task{
	int d,w;	 
}a[505];
bool cmp(task t1,task t2)
{
	return t1.w>t2.w;
}
int main(){
	int d[505];
	int w[505];
	int n;
	cin>>n;
	for(int i=0;i<n;i++)
		cin>>a[i].d;
	int total=0;
	for(int i=0;i<n;i++)
	{
		cin>>a[i].w;
		total+=a[i].w;
	}
	sort(a,a+n,cmp);
	int flag[505]={0};
	int ans=0;  
	for(int i=0;i<n;i++){
		for(int j=a[i].d-1;j>=0;j--){
			if(flag[j]==0){
				ans+=a[i].w;
			    flag[j]=1;
				break;
			}
		}
	}
	cout<<total-ans;
	return 0;
} 

Problem E. sqy 的锡纸烫

时间限制 1000 ms
内存限制 64 MB

题目描述

前不久 sqy 老师花了大价钱,去做了一个帅气的锡纸烫。有着商业眼光的 sqy 一下子发现了大商机,于是他自己开了一家美容美发店。

sqy 找了刚刚做完纹理烫的大预言家 cbj 预测了未来,发现每个顾客都只在白天来美发店,并且第一次来店里的时候都会充一次价值 xi 的卡,然后从第二天开始,每天白天都会来这里打理头发,而 sqy 仅收取成本价 1 元钱来吸引顾客,直到把卡掏空为止,这个顾客就再也不会回来。

黑心商人 sqy 找大预言家要来了每个顾客的充卡时间和充值金额,他准备在某一天晚上跑路,他想知道自己最多能卷走多少钱。

输入数据

第一行包括一个整数 n(1≤n≤105)表示有 n 个顾客。 接下来共 n 行,每i+1行包括两个整数 xi,yi 表示第 xi 天一个顾客来充值了 yi 元 (1≤xi≤106,0≤yi≤231−1)。

输出数据

输出一行包括一个整数 ans, 表示 sqy 最多能卷走多少钱。

样例输入

5
1 5
2 5 
3 5
4 5
5 5

样例输出

15

样例说明

在第五天的时候,第一个人消费4元还剩1元,第二个人消费3元还剩2元,第三个人消费2元还剩3元,第四个人消费1元还剩4元,第五个人还没有开始消费就被卷钱跑路了。

#include <stdio.h>
#include <math.h>
#include <string.h> 
#define MAX 1000010
using namespace std;

long long my_min(long long a,long long b){
	if(a<b) return a;
	else return b;
}

long long my_max(long long a,long long b){
	if(a<b) return b;
	else return a;
}

long long in[MAX];//收益 
long long out[MAX]; //花费 
long long ans =0;

int main(){
	for (int i=0;i<1000010;i++) 
        in[i] = out[i] = 0;
	long long n;
	scanf("%lld",&n);
	long long day,income;
    
	for(int i=1;i<=n;i++){
		scanf("%lld%lld",&day,&income);
		in[day]+=income;
		out[day+1]--;
		long long leave=my_min(day+income+1,MAX);
		out[leave] ++;
	} 
    
	long long cur_out=0;//当天理发人数
	long long cur_in=0;//当天收益
	
	for(int i=1;i<=MAX-10;i++){
		cur_out+=out[i];
		cur_in=cur_in+in[i]+cur_out;
		ans=my_max(ans,cur_in);
	} 
	printf("%lld",ans);	
	return 0;	
} 

第七次算法作业

Problem A. 海战

时间限制 1000 ms
内存限制 128 MB

题目描述

在这个著名的游戏中,在一个方形的盘上放置了固定数量和形状的船只,每只船却不能碰到其它的船。在这个题中,我们仅考虑船是方形的,所有的船只都是由图形组成的方形。编写程序求出该棋盘上放置的船只的总数。

输入数据

输入文件头一行由用空格隔开的两个整数 R 和 C 组成 ,1≤R,C≤1000,,1≤R,C≤1000, 这两个数分别表示游戏棋盘的行数和列数。接下来的 R 行每行包含 C 个字符,每个字符可以为“#”,也可为“.”,“#”表示船只的一部分,“.”表示水。

输出数据

为每一个段落输出一行解。如果船的位置放得正确(即棋盘上只存在相互之间不能接触的方形,如果两个“#”号上下相邻或左右相邻却分属两艘不同的船只,则称这两艘船相互接触了)。就输出一段话“There are S ships.”,S表示船只的数量。否则输出“Bad placement.”。

样例输入

6 8
.....#.#
##.....#
##.....#
.......#
#......#
#..#...#

样例输出

There are 5 ships.

方法1

  • 从左到右,从上到下进行遍历,如果是”#“(说明可能出现方形),则以该点作为基准点S(i,j)进行,进入下一步的判断
  • 以基准点向右,向下扫描单行和单列,找到连续出现”#“最大长度y和宽度x。
  • 判断以(i,j)为左上角的顶点,长宽分别为x,y的区域是否为方形,以及是否与其他的区域相邻,即是否满足如下的两个条件
    • 遍历i+1~i+x-1行,每一行连续的宽度==x 以及该行j-1列的值!=’#‘
    • 遍历j~j+x-1列,每一列连续的长度==y以及该列j-1列的值!=’#‘
  • 如果不满足,则输出Bad Placement, 否则ans++
#include<iostream>
using namespace std;
char a[1000][1000];

int r, c;
int ans = 0;
int  check(int i, int j) {
	int m,n;
	int x=0, y=0;//行,数
	for (m = i; m < r && a[m][j] == '#'; m++) {
		if (j > 0 && a[m][j - 1] == '#')
			break;
		x++;
	}
		
	for (n = j; n < c && a[i][n] == '#'; n++) {
		if (i > 0 && a[i - 1][n] == '#')
			break;
		y++;
	}
		
	for (m = i; m < i + x; m++) {
		int tx = 0;
		for (n = j; n < c && a[m][n]=='#'; n++) {
			tx++;
		}
		if (tx != y)
			return 0;
	}
	for (n = j; n < j + y; n++) {
		int ty = 0;
		for (m = i; m < r&&a[m][n]=='#'; m++) {
			ty++;
		}
		if (ty != x)
			return 0;
	}
	for (m = i; m < i + x; m++)
		for (n = j; n < j + y; n++)
			a[m][n] = '.';

	ans++;
	return 1;
}
int main() {
	cin >> r >> c;
	for (int i = 0; i < r; i++) {
		for (int j = 0; j < c; j++) {
			cin >> a[i][j];
		}
	}
	for (int i = 0; i < r; i++) {
		for (int j = 0; j < c; j++) {
			if (a[i][j] == '#') {
				int flag = check(i, j);
				if (flag == 0) {
					cout << "Bad placement." << endl;
					return 0;
				}
			}
		}
	}
	cout << "There are " << ans << " ships." << endl;
	return 0;
}

方法2

方法1的check 函数复杂度高,每一行和每一列都要进行遍历。方法2则通过寻找子结构来简化判断。可以发现在任意一个2*2的区域内如果出现了3个#, 则一定是有船相邻。方法2对check 进行优化。

#include<iostream>
using namespace std;
char a[1000][1000];

int r, c;
int ans = 0;
int  check(int i, int j) {
	int cnt=0;
    if(g[x][y]=='#') cnt++;
    if(g[x+1][y]=='#') cnt++;
    if(g[x][y+1]=='#') cnt++;
    if(g[x+1][y+1]=='#') cnt++;
    return cnt==3;
}
int main() {
	cin >> r >> c;
	for (int i = 0; i < r; i++) {
		for (int j = 0; j < c; j++) {
			cin >> a[i][j];
		}
	}
	for (int i = 0; i < r; i++) {
		for (int j = 0; j < c; j++) {
			if (a[i][j] == '#') {
				int flag = check(i, j);
				if (flag == 0) {
					cout << "Bad placement." << endl;
					return 0;
				}
			}
		}
	}
	cout << "There are " << ans << " ships." << endl;
	return 0;
}

方法3

方法3则是通过dfs算法,即每一次深搜的时候把与#连通的所有点改成*因为它们是同一艘船

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int r,c;
char map[1010][1010];
int fx[4]={0,-1,1,0};
int fy[4]={-1,0,0,1};

int dfs(int x,int y){
	map[x][y]='*';
    for(int i=0;i<4;i++){
        if(x+fx[i]>0 && x+fx[i]<=r && y+fy[i]>0 && y+fy[i]<=c && map[x+fx[i]][y+fy[i]]=='#') dfs(x+fx[i],y+fy[i]);
    }
} //把与#连通的所有点改成*因为它们是同一艘船

int  check(int i, int j) {
	int cnt=0;
    if(g[x][y]=='#') cnt++;
    if(g[x+1][y]=='#') cnt++;
    if(g[x][y+1]=='#') cnt++;
    if(g[x+1][y+1]=='#') cnt++;
    return cnt==3;
}

int main(){
	scanf("%d%d",&r,&c);
    int i,j;
	for(i=1;i<=r;i++){
		for(j=1;j<=c;j++){
		cin>>map[i][j];
		}
	}
	int s=0;
	for(i=1;i<=r;i++){
		for(j=1;j<=c;j++){
			if(i<r&&j<c&&check(i,j)==0){
				printf("Bad placement.");
				return 0; //不合法后面就没必要继续了 
			}
		}
	}
	for(i=1;i<=r;i++){
		for(j=1;j<=c;j++){
			if(map[i][j]=='#'){
                s++;
                dfs(i,j);	
			}//因为前面已经确保了是合法的,现在只需统计船的数量 
		}
	}
	printf("There are %d ships.",s);
	return 0;
}

Problem B. 课堂作业-9-2

时间限制 1000 ms
内存限制 64 MB

题目描述

在一个被分成n*n个格子的平原上,有一些格子有铁矿,两格铁矿如果相邻那么就认为他们属于同一个矿床,每个矿床都包含一个或更多个铁矿,问一共有几个矿床。
两个格子只要有公共边或公共点就算相邻。

输入数据

第一行为一个正整数n,n<=1000 接下来有n行,每行有n个字符,表示平原的对应位置有没有铁矿,*代表没有,#代表有

输出数据

矿床个数

样例输入

6
*#*###
###*#*
*##***
*#****
***###
******

样例输出

2

样例说明

最下面三块铁矿属于一个矿床,其他铁矿属于一个矿床,所以一共有两个矿床

方法1

典型的dfs, 每次搜索周围的8个点

#include<bits/stdc++.h>
using namespace std;
#define maxn 1005

int n;
string a[maxn];

int di[] = {0, 0, 1, -1, 1, 1, -1, -1};
int dj[] = {1, -1, 0, 0, 1, -1, 1, -1};
bool inB(int i, int j){
    return 1<=i&&i<=n&&1<=j&&j<=n;
}

void dfs(int i, int j){
    a[i][j] = '*';
    for(int k = 0; k<8; k++){
        int ii = i+di[k], jj = j+dj[k];
        if(!inB(ii, jj)||a[ii][jj]=='*') continue;
        dfs(ii, jj);
    }
}

int main(){
    ios::sync_with_stdio(false); cin.tie(0);

    cin>>n;
    for(int i = 1; i<=n; i++){
        cin>>a[i];
        a[i] = " "+a[i];
    }

    int cnt = 0;
    for(int i = 1; i<=n; i++){
        for(int j = 1; j<=n; j++){
            if(a[i][j]=='*') continue;
            cnt++;
            dfs(i, j);
        }
    }

    cout<<cnt;
    return 0;
}

Problem C. 课堂作业-9-4

时间限制 1000 ms
内存限制 64 MB

题目描述

众所周知,八皇后问题是一个非常经典的算法问题,现在将题目改为在n*m的棋盘上,放min(n,m)个互不攻击的皇后,问有多少种放法。

输入数据

两个正整数n,m,n和m均不超过12

输出数据

方案数

样例输入

2 3

样例输出

2

方法1

思路:一行一行得进行摆放,用for循环来确定每一行中摆放的列数。用check判断第j行的摆放位置与1~j-1行不冲突。

#include <iostream>
using namespace std;
int n, m;
int a[15];
int res = 0;
int check(int i, int j) {
	int j1 = j, i1 = i, ok1 = 1;
	while ((j1 > 1) && ok1) {
		j1--;
		if (a[j1] == i)
			ok1 = 0;
	}
	j1 = j; i1 = i;
	while ((j1 > 1) && (i1 > 1) && ok1) {
		j1--; i1--;
		if (a[j1] == i1)
			ok1 = 0;
	}
	j1 = j; i1 = i;
	while ((j1>1) && (i1 < n) && ok1) {
		j1--; i1++;
		if (a[j1] == i1)
			ok1 = 0;
	}
	return ok1;
}
void queue(int j) {
	if (j > m)
	{
		res++;
	}
	else {
		for (int i = 1; i <= n; i++) {
			if (check(i, j)) {
				a[j] = i;
				queue(j + 1);
			}
		}
	}
}
int main() {
	cin >> n >> m;
	int temp;
	if (n < m) {
		temp = n;
		n = m;
		m = temp;
	}
	queue(1);
	cout << res;
}

方法2

思路:dfs

一行一行得进行摆放,用for循环来确定每一列,由于是一行一行得摆放所以不可能同行,我们只需要标记同列,同对角线,就行,会发现主对角线一条对角线上的行列和等于同一个常数,副对角线一条对角线行列差等于一个常数,只不过会是负数,防止下标是负数我们可以进行+n。

#include<bits/stdc++.h>
using namespace std;
#define maxn 50

int n, m;
int a[maxn][maxn];
const int bias = 25;
int visc[maxn], vis1[maxn], vis2[maxn];//表示这一列,主对角线,负对角线有没有皇后
int cnt;
void dfs(int i){
    if(i>n){
        cnt++;
        return;
    }
    for(int j = 1; j<=m; j++){
        if(visc[j]||vis1[i+j]||vis2[i-j+bias]) continue;
        visc[j] = vis1[i+j] = vis2[i-j+bias] = 1;
        dfs(i+1);
        visc[j] = vis1[i+j] = vis2[i-j+bias] = 0;
    }
}

int main(){
    ios::sync_with_stdio(false); cin.tie(0);
    cin>>n>>m;
    if(n>m) swap(n, m);
    dfs(1);
    cout<<cnt;
    return 0;
}

Problem D. 课堂作业-8-4

时间限制 1000 ms
内存限制 64 MB

题目描述

给一个n个点,m条边的无向图,和一个起点s,一个终点t,问能不能从s经过一些边走到t,能走到则输出Yes,否则输出No
点的编号从1到n

输入数据

第一行为两个正整数n,m,n<=1e5,m<=1e5 接下来m行每行两个正整数t1,t2,代表t1到t2有一条无向边 最后一行为两个正整数s和t

输出数据

Yes或No

样例输入

3 2
1 2
2 3
1 3

样例输出

Yes

方法1

#include<bits/stdc++.h>
using namespace std;
#define maxn 100005

int n, m;
vector<int> G[maxn];
int vis[maxn];

void dfs(int u){
    if(vis[u]) return;
    vis[u] = 1;
    for(auto v:G[u]){
        dfs(v);
    }
}

int main(){
    ios::sync_with_stdio(false); cin.tie(0);
    cin>>n>>m;
    for(int i = 1; i<=m ;i++){
        int u, v; cin>>u>>v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    int s, t; cin>>s>>t;
    dfs(s);
    cout<<(vis[t]?"Yes\n":"No\n");

    return 0;
}

Problem E. 24点游戏

时间限制 1000 ms
内存限制 128 MB

题目描述

几十年前全世界就流行一种数字扑克游戏,至今仍有人乐此不疲.在中国我们把这种游戏称为“算24点”。您作为游戏者将得到4个1-13(在扑克牌里用A代替1,J代替11,Q代替12,K代替13)之间的自然数作为操作数,而您的任务是对这4个操作数进行适当的算术运算(可以使用+、-、*、/、括号),判断运算结果是否等于24。能输出1,不能输出0。

输入数据

四个牌面值。牌面值与牌面值之间用一个空格隔开。

输出数据

输出 0 或 1 。

样例输入

3 8 10 Q

样例输出

1

样例说明

Q×(10/(8-3))=24

方法1

#include <iostream>
#include <vector>
using namespace std;

double nums[4];
int vist[4] = {0};

bool dfs(double res) {
    double num;
    bool last = true;
    for (int i = 0; i < 4; ++i) {
        if (!vist[i]) {

            vist[i] = true;
            num = nums[i];
            if (res) {
                if (dfs(res + num) || dfs(res - num) || dfs(num - res) ||
                    dfs(res * num) || dfs(res / num) || dfs(num / res)) {
                    return true;
                }
            }
            else {
                if (dfs(num)) {
                    return true;
                }
            }

            vist[i] = false;
            last = false;
        }
    }
    return last && res == 24;
}

int main() {
    string tmp;
    for (int i = 0; i < 4; ++i) {
        cin >> tmp;
        if (tmp == "A") {
            nums[i] = 1;
        } else if (tmp == "J") {
            nums[i] = 11;
        } else if (tmp == "Q") {
            nums[i] = 12;
        } else if (tmp == "K") {
            nums[i] = 13;
        } else {
            nums[i] = atoi(tmp.c_str()); 
        }
    }

    if (dfs(0)) {
        cout << 1 << endl;
    } else {
        cout << 0 << endl;
    }

    return 0;
}


这篇关于算法作业题解的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程