算法作业题解

文章目录

  • 第一次算法作业
    • Problem A. 思维之花-方程
    • Problem C. 摆三角形(枚举)
  • 第二次算法作业
    • 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. 传球游戏(动规)
  • 第五次算法作业
    • Problem A. 单行最大子矩阵和问题(动规)
    • Problem B. 多行最大子矩阵和问题(动规)
    • Problem C. 小明的打工计划 (动规)
    • Problem D. 邮票面值设计(动规)
    • Problem E. 导弹拦截(动规)
  • 第六次算法作业
    • Problem A. 合并果子(贪心)
    • Problem B. 最小差距(贪心)
    • Problem C. 上帝的爱好(贪心)
    • Problem D. 任务调度问题(贪心)
    • Problem E. sqy 的锡纸烫(贪心)
  • 第七次算法作业
    • Problem A. 海战(dfs)
    • Problem B. 矿床个数(dfs)
    • Problem C. 8皇后(dfs)
    • Problem D. 无向图的连通性(dfs)
    • Problem E. 24点游戏(dfs)
  • 期中测试
    • Problem B. 等价表达式
    • Problem C. 求解多元一次方程组
    • Problem E. 最佳课题选择(动规)
    • Problem F. 逆序对的个数()
        • 租骆驼
        • 骆驼补给
        • 移掉k位数字
        • 传纸条
        • 序列划分
        • 分考场
        • lucky number
        • 带行李
        • 选数
        • 欧几里得游戏
        • 野猫和胖子
        • 晴天小猪
        • 顺治滑雪
        • 磊城堡
        • 铺地毯
        • 回文数

第一次算法作业

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

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. 摆三角形(枚举)

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

题目描述

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

输入数据

第一行为一个正整数n,3=

输出数据

三角形周长的最大值

样例输入

5
1 2 3 4 5

样例输出

12

解法1:

#include
#include
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. 立方质数(枚举)

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

题目描述

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

输入数据

正整数n,n<=1000

输出数据

Yes或者No

样例输入

19

样例输出

Yes

解法1

#include 
#include 
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. 开灯(模拟)

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

题目描述

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

输入数据

一个正整数n,n<100000

输出数据

亮着的灯的数量

样例输入

5

样例输出

2

解法1:

#include 
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 
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. 键盘打字(模拟)

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

题目描述

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

输入数据

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

输出数据

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

样例输入

xiechengxuz[henhao]wan

样例输出

henhaoxiechengxuzwan

样例说明

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

解法1:

# include 
# include 
# include 
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. 约翰的母牛(枚举)

时间限制 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
#include
#include 
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 
#include 
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
using namespace std;
int main()
{
    unsigned long long x;
    cin >> x;
    cout << ((x & 0x0000ffff) << 16 | (x & 0xffff0000) >> 16) << endl;
}

第三次算法作业

Problem A. 切割木棍(二分)

时间限制 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 
using namespace std;
#include 
#include 
#include 
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. 移除石头(二分)

时间限制 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 
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. 上台阶(动态规划)

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

题目描述

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

输入数据

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

输出数据

方案数

样例输入

3

样例输出

4

解法1

# include 
# include 
# include 
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 
#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. 移动圆盘

时间限制 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 
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 
#include 
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 
# include 
# include 
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 
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
#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
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 
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 
#include 
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 
#include 
#include 
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
using namespace std;
#define MAXN 500 
#define MAXNN 2000
#define INT register int

int n,k,ans=0;
int f[MAXNN],temp[MAXN],fin[MAXN];

inline int read()
{
    int s=0,w=1;char c=getchar();
    while (c<'0' || c>'9') c=getchar();
    while (c>='0' && c<='9') {s=s*10+c-48; c=getchar();}
    return s*w;
}

int DP(int N,int sum)
{
    memset(f,127,sizeof(f));
    f[0]=0;
    INT i,j;
    for (i=1;i<=N;++i)
        for (j=temp[i];j<=sum*n;++j)
            f[j]=min(f[j],f[j-temp[i]]+1);
    i=1;
    for (i=1;i<=sum*n;++i) if (f[i]>n) return i-1;
    return n*sum;
}

void dfs(int take,int MAX,int sum)
{
    if (take==k)
    {
        if (MAX>ans)
        {
            ans=MAX;
            for (int i=1;i<=k;++i) fin[i]=temp[i];
        }
        return;
    }
    INT i;
    for (i=temp[take]+1;i<=MAX+1;++i)
    {
        temp[take+1]=i;
        int MAXnum=DP(take+1,sum+i);
        dfs(take+1,MAXnum,sum+i);
    }
    return;
}

int main()
{
    n=read();k=read();
    temp[1]=1;
    dfs(1,n,1);
    for (int i=1;i<k;++i) printf("%d ",fin[i]);
    printf("%d\nMAX=%d\n",fin[k],ans);
    return 0;
}

Problem E. 导弹拦截(动规)

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

题目描述

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

输入数据

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

输出数据

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

样例输入

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

样例输出

6,1

解法1

#include
#include
#include
#include
using namespace std;
#define N 9999
int down[N];//down[i]表示以i+1个字符结尾的最长递减子序列
int up[N];//up[i]表示以i+1个字符结尾的最长非递减子序列
int h[N];//表示高度
int n=0;

int main(){
	while(cin>>h[n++]);
	n--;
	for(int i;i<n;i++)
	   down[i]=up[i]=1;//最坏情况下自身也是一个串
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<i;j++)
		{
			if(h[i]<h[j])
				 down[i]=max(down[i],down[j]+1);
			else
				 up[i]=max(up[i],up[j]+1);
		}
	}

 int maxd=0,maxu=0;
	for(int i=0;i<n;i++){
		maxd=max(maxd,down[i]);
		maxu=max(maxu,up[i]);
      }
	cout<<maxd<<endl<<maxu<<endl;
	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) 是第 i种果子的数目。

输出数据

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

样例输入

3 
1 2 9

样例输出

15 

解法1

#include
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
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 
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] &&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 
#include 
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 
#include 
#include  
#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. 海战(dfs)

时间限制 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
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
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
#include
#include
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 x, int y) {
	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. 矿床个数(dfs)

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

题目描述

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

输入数据

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

输出数据

矿床个数

样例输入

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

样例输出

2

样例说明

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

方法1

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

#include
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. 8皇后(dfs)

时间限制 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 
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
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. 无向图的连通性(dfs)

时间限制 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
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点游戏(dfs)

时间限制 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 
#include 
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;
}

期中测试

Problem B. 等价表达式

时间限制 1000 ms
内存限制 128 MB
题目描述
  明明进了中学之后,学到了代数表达式。有一天,他碰到一个很麻烦的选择题。这个题目的题干中首先给出了一个代数表达式,然后列出了若干选项,每个选项也是一个代数表达式,题目的要求是判断选项中哪些代数表达式是和题干中的表达式等价的。

这个题目手算很麻烦,因为明明对计算机编程很感兴趣,所以他想是不是可以用计算机来解决这个问题。假设你是明明,能完成这个任务吗?

这个选择题中的每个表达式都满足下面的性质:

1. 表达式只可能包含一个变量‘a’。

2. 表达式中出现的数都是正整数,而且都小于10000。

3. 表达式中可以包括四种运算‘+’(加),‘-’(减),‘’(乘),‘’(乘幂),以及小括号‘(’,‘)’。小括号的优先级最高,其次是‘’,然后是‘’,最后是‘+’和‘-’。‘+’和‘-’的优先级是相同的。相同优先级的运算从左到右进行。(注意:运算符‘+’,‘-’,‘*’,‘^’以及小括号‘(’,‘)’都是英文字符)

4. 幂指数只可能是1到10之间的正整数(包括1和10)。

5. 表达式内部,头部或者尾部都可能有一些多余的空格。

下面是一些合理的表达式的例子:

((a^1) ^ 2)^3,aa+a-a,((a+a)),9999+(a-a)a,1 + (a -1)3,110^9……

对于30%的数据,表达式中只可能出现两种运算符‘+’和‘-’;

对于其它的数据,四种运算符‘+’,‘-’,‘*’,‘^’在表达式中都可能出现。

对于全部的数据,表达式中都可能出现小括号‘(’和‘)’。

输入数据
  输入的第一行给出的是题干中的表达式。第二行是一个整数 n (2≤n≤26), 表示选项的个数。后面 n 行,每行包括一个选项中的表达式。这 n 个选项的标号分别是 A,B,C,D……

输入中的表达式的长度都不超过 50 个字符,而且保证选项中总有表达式和题干中的表达式是等价的。
输出数据
  输出包括一行,这一行包括一系列选项的标号,表示哪些选项是和题干中的表达式等价的。选项的标号按照字母顺序排列,而且之间没有空格。
样例输入
( a + 1) ^2
3
(a-1)^2+4*a
a + 1+ a
a^2 + 2 * a * 1 + 1^2 + 10 -10 +a -a
样例输出
AC

#include 
#include 
using namespace std;

int l[200];
const int mod = 1e9 + 7, x = 173;
stack<int>num;
stack<char>opera;
string s;

string read() {
    string s;
    char ch;
    do {
        ch = getchar();
    } while (ch == '\n' || ch == '\r' || ch == ' ');

    while (ch != '\n' && ch != '\r') {
        if (ch != ' ') s += ch;
        ch = getchar();
        if (ch == EOF) break;
    }
    return s;
}


int get(int n1, int n2) {
    unsigned long long ans = 1, pow = n1;
    while (n2) {
        if (n2 & 1) ans = ans * pow % mod;
        pow = pow * pow % mod;
        n2 >>= 1;
    }
    int res = ans % mod;
    return res;
}

void jisuan() {
    int op1, op2;
    op1 = num.top();//取数字栈中的两个数,注意顺序是n2 (运算符) n1
    num.pop();
    op2 = num.top();//取数字栈中的两个数
    num.pop();
    int aa;
    switch (opera.top()) {
    case '^':aa = get(op2, op1); break;
    case '+':aa = (op1 + op2) % mod;  break;
    case '-':aa = (op2 - op1 + mod) % mod;  break;
    case '*':aa = ((long long)(op2 % mod) * (op1 % mod)) % mod; break;
    }
    num.push(aa);
    opera.pop();
}

void pushStack() {
    int len = (int)s.length(), sum = 0;
    for (int i = 0; i < len; i++) {
        if (s[i] == 'a') num.push(x);
        else if (isdigit(s[i])) {
            sum = sum * 10 + (s[i] - '0');
            if (i == len - 1 || !isdigit(s[i + 1])) num.push(sum), sum = 0;
        }
        else {
            if (s[i] == '(') {
                opera.push('(');
                continue;
            }
            else if (s[i] == ')') {
                while (!opera.empty() && opera.top() != '(') jisuan();
                if (!opera.empty() && opera.top() == '(') opera.pop();
                continue;
            }
            while (!opera.empty() && l[s[i]] <= l[opera.top()])  jisuan();
            opera.push(s[i]);
        }
    }
}

int main() {
    l['^'] = 4; l['*'] = 3; l['+'] = 2; l['-'] = 2;//初始化运算符优先级
    s = read();
    int n, ans;
    pushStack();//形成后缀表达式
    while (!opera.empty()) {
        if (opera.top() == '(') {//如果括号不匹配(多出括号的情况)
            opera.pop(); continue;
        }
        jisuan();
    }
    ans = num.top();//记录原表达式的值
    cin >> n;
    for (int i = 0; i < n; i++) {
        while (!num.empty()) num.pop();//记得清空栈
        while (!opera.empty()) opera.pop();
        s = read();
        pushStack();//形成后缀表达式
        while (!opera.empty()) {
            if (opera.top() == '(') {//如果括号不匹配(多出括号的情况)
                opera.pop(); continue;
            }
            jisuan();
        }
        char result;
        if (num.top() == ans) {
            result = 'A' + i;
                cout << result;
        }
    }
    return 0;
}

Problem C. 求解多元一次方程组

时间限制 1000 ms
内存限制 128 MB
题目描述
  贾老二是个品学兼优的好学生,但由于智商问题,算术学得不是很好,尤其是在解方程这个方面。虽然他解决 2x=2 这样的方程游刃有余,但是对于 {x+y=3 x-y=1} 这样的方程组就束手无策了。于是他要你来帮忙。前提是一次方程组且保证在integer的范围内可以处理所有问题。

输入数据
  第一行一个数字 N (1≤N≤100) 表示要求的未知数的个数,同时也是所给的方程个数。
  第 2 到 N+1 行,每行 N+1 个数。前 N 个表示第 1 到 N 个未知数的系数。第 N+1 个数表示 N 个未知数乘以各自系数后的加和。(保证有唯一整数解)
输出数据
  一行 N 个数,表示第 1 到 N 个未知数的值。
样例输入
2
1 1 3
1 –1 1
样例输出
2 1

#include
#include
#include
using namespace std;
const double eps=1e-6;

double a[100][100],b[100];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
            scanf("%lf",&a[i][j]);
        scanf("%lf",&b[i]);
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=i;j<=n;j++)
            if(fabs(a[j][i])>eps)
            {
                for(int k=1;k<=n;k++)
                    swap(a[j][k],a[i][k]);
                swap(b[i],b[j]);
            }
        for(int j=1;j<=n;j++)
        {
            if(i==j)
                continue;
            double rate=a[j][i]/a[i][i];
            for(int k=1;k<=n;k++)
                a[j][k]-=a[i][k]*rate;
            b[j]-=b[i]*rate;
        }
    }
    for(int i=1;i<n;i++)
    {
        double ans=b[i]/a[i][i];
        if(fabs(ans)<eps)
            printf("%.0lf ",fabs(ans));
        else
            printf("%.0lf ",ans);
    }
    double ans=b[n]/a[n][n];
    if(fabs(ans)<eps)
        printf("%.0lf",fabs(ans));
    else
        printf("%.0lf",ans);
    return 0;
}

Problem E. 最佳课题选择(动规)

时间限制 1000 ms
内存限制 128 MB
题目描述
  Matrix67要在下个月交给老师n篇论文,论文的内容可以从m个课题中选择。由于课题数有限,Matrix67不得不重复选择一些课题。完成不同课题的论文所花的时间不同。具体地说,对于某个课题i,若Matrix67计划一共写x篇论文,则完成该课题的论文总共需要花费Ai*x^Bi个单位时间(系数Ai和指数Bi均为正整数)。给定与每一个课题相对应的Ai和Bi的值,请帮助Matrix67计算出如何选择论文的课题使得他可以花费最少的时间完成这n篇论文。

输入数据
  第一行有两个用空格隔开的正整数 n 和 m, 分别代表需要完成的论文数和可供选择的课题数。
  以下 m 行每行有两个用空格隔开的正整数。其中,第 i 行的两个数分别代表与第 i 个课题相对应的时间系数 Ai 和指数 Bi 。
  对于 30 的数据 ,n≤10,m≤5;
  对于100%的数据 ,n≤200,m≤20,Ai≤100,Bi≤5 。
输出数据
  输出完成 n 篇论文所需要耗费的最少时间。
样例输入
10 3
2 1
1 2
2 1
样例输出
19

#include
using namespace std;
long long dp[210];
long long a[30],b[30];

long long findmin(long long x,long long y){
	if(x<y) 
		return x;
	else 
		return  y;
}
long long get(long long a,long long b)//这个是快速幂 其实可以不要 因为b[ i ]很小  
{
    if(b==1) return a;
    long long now=a,t=b,res=1;
    while(t)
    {
        if(t&1) res*=now;
        now*=now;
        t>>=1;
    }
    return res;
}
int main()
{
	long long m,n;
    std::ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        cin>>a[i]>>b[i];
    }
    memset(dp,0x7f,sizeof dp);
    dp[0]=0;
    for(int i=1;i<=m;i++)// 课题数
    	for(int j=n;j>0;j--) // 论文数
   			 for(int t=j;t>0;t--) // 该课题对应的论文数
    			dp[j]=findmin(dp[j],dp[j-t]+a[i]*get(t,b[i]));
    cout<<dp[n];
    return 0;
}

Problem F. 逆序对的个数()

时间限制 1000 ms
内存限制 256 MB
题目描述
Ricky班里有n(2<=n<=100000)个人,每个人有一个学号ai(1<=ai<=n),保证学号ai互不相同。Ricky手里有一张班级合影,他发现虽然大家是按身高从低到高排好队的,但如果按学号看的话却不一定是从小到大,他想看一看如果按照学号来看,这个队排的有多乱。Ricky把混乱度定义为队列中逆序对的个数,即:如果从前往后看,大家正好是按照学号从小到大排列的,那逆序对为0个,混乱度为0;而每能找到两个人形成了学号大同学的在前,学号小的在后(即i < j且ai > aj),就称其为一个逆序对,混乱度计数也要加1。由于Ricky班里人可能很多,Ricky实在数不过来了,现在告诉你合影上同学们的学号(按从前往后),请你帮忙编写程序计算一下混乱度,满足一下Ricky的好奇心。

输入数据
第一行有一个整数n(2<=n<=100000),表示Ricky班上的人数; 第二行有n个整数a1,a2,…,an(1<=ai<=n),表示合影上同学们的学号。

输出数据
输出一个整数,表示合影上排列的混乱度(逆序对个数)

样例输入
5
1 3 4 2 5
样例输出
2
样例说明
其中(3,2) (4,2)形成两个逆序对,所以混乱度为2
算法作业题解_第1张图片

#include
#include
using namespace std;
int n, a[100010], c[100010];
long long ans;

void mergesort(int b, int e)
{
    if (b == e)
        return;
    int mid = (b + e) / 2, i = b, j = mid + 1, k = b;
    mergesort(b, mid), mergesort(mid + 1, e);
    while (i <= mid && j <= e)
        if (a[i] <= a[j])
            c[k++] = a[i++];
        else
            c[k++] = a[j++], ans += mid - i + 1;
    while (i <= mid)
        c[k++] = a[i++];
    while (j <= e)
        c[k++] = a[j++];
    for (int l = b; l <= e; l++)
        a[l] = c[l];
}

int main()
{
    scanf("%d", &n);
    for (int i = 0; i < n; i++)
        scanf("%d", &a[i]);
    mergesort(0, n - 1);
    printf("%lld", ans);
    return 0;
}


#### 树木生长

新生的树枝次年休息,问n年后会长出多少树枝

~~~cpp
// 分治算法 复杂度 O(2^n)
int fun(int n){
	if(n==1||n==2)
        return 1;
    else return fun(n-1)+fun(n-2);
}
//动态规划  复杂度O(N)
int fun(int n){
	dp[0]=dp[1]=1;
    for(int i=3;i<n;i++)
        dp[i]=dp[i-1]+dp[i-2];
}

租骆驼

有N个骆驼出租点,1,2,…N, 从i到j的租金为C(i,j), 求1到N的最小租金

//动态规划
// dp[i][j]:表示从出租店i到达出租店j需要的最小租金
int fun(int N){
	for(int i=1;i<=n;i++)
        dp[i][i]=0;
    for(int len=2;len<=n;len++){
        for(int i=1;i<=n-len+1;i++){
            j=n-len+1;
            dp[i][j]=1e6;
            for(int k=i+1;k<j;k++)
                long ans=dp[i][k]+dp[k][j];
            	if(ans<dp[i][j])
                    dp[i][j]=ans;
        }
        if(a[i][j]<dp[i][j])
            dp[i][j]=a[i][j];
	}
    return dp[1][n];
}

// 回溯算法
int ans=1e6; 
int c[1000][1000]; //花费
int backtracking(int n,int sum,int startIndex){
    if(startIndex==n){
         if(sum<ans)
             ans=sum;
        return;
    }
    for(int i=startIndex+1;i<n;i++){
        if(sum>ans) continue;
        sum+=c[startIndex][i];
        backtracking(n,sum,i+1)
        sum-=c[startIndex][i];
    }  
}
int main(){
    int sum=0;
    backtracking(n,sum,1)
}

// dfs + 剪枝 = 回溯
void dfs(int t,int step){
    if(step>ans)
        return; //减枝
    if(t==n){
        if(ans>step)
            ans=step;
        return;
    }
    for(int i=t+1;i<=n;i++)
        dfs(i,step+c[t][i])
}

骆驼补给

有N个骆驼补给站,补给一次后最多可以旅行K公里,给定任意两个补给站之间的距离,设计算法求出最小的补给次数,并证明

int d[N][N], K, cnt=0;
void greedy(){
    for(int i=0;i<n;i++){
        if (d[i]>k)
            printf("No solution!\n");
        return;
    }
    int temp=0;
    for(int i=0;i<n;i++){
        temp+=d[i];
        if(temp>k){
            cnt++;
            temp=d[i];
        }
    }
}

证明:设 b i b_i bi为第i次补给的补给站的编号,设上述贪心算法给出的解为 B = b 1 , b 2 , . . . b n B={b_1,b_2,...b_n} B=b1,b2,...bn ,现证明B为最优解。

B i = b 1 , . . b i B_i={b_1,..b_i} Bi=b1,..bi,用归纳法证明对于任意的1<=i<=n, 都存在一个最优解包含 B i B_i Bi, 则另i=n,我们就证明了该命题

基础步:i=1时,用反证法证明,假设不存在最优解包含 B 1 = b 1 B_1={b_1} B1=b1, 则对于任何一个最优解 B ∗ = b 1 ∗ , b 2 ∗ , . . . b n ∗ B*={b^*_1,b^*_2,...b^*_n} B=b1,b2,...bn 则有 b 1 ! = b 1 ∗ b_1!=b^*_1 b1!=b1, 根据贪心规则,必有 b 1 > b ∗ − 1 b_1>b^*-1 b1>b1,所以用 B 1 B_1 B1代替 b ∗ b^* b中的 b 1 ∗ b^*_1 b1,此时 b 1 , b 2 ∗ , . . . b n ∗ {b_1,b^*_2,...b^*_n} b1,b2,...bn必定也为最优解,所以结论成立。

归纳步:假设i=k时成立,先证明i=k+1时成立。根据归纳假设,存在一个包含 B k B_k Bk的最优解,设为 B ∗ = b 1 , . . . , b k , b k ∗ + 1 , b k ∗ + 2 B*={b_1,...,b_k,b^*_k+1,b^*_k+2} B=b1,...,bk,bk+1,bk+2, 按照贪心的规则,我们有 b k + 1 > b k ∗ + 1 b_k+1>b^*_k+1 bk+1>bk+1,所以用 b k + 1 b_k+1 bk+1替换B*中的 b ∗ k + 1 b*_k+1 bk+1, 此时 b 1 , . . . , b k , b k ∗ + 1 , b k ∗ + 2 {b_1,...,b_k,b^*_k+1,b^*_k+2} b1,...,bk,bk+1,bk+2必定也为最优解,所以结论成立。综上所述,该命题成立。

移掉k位数字

n位的整数a, 去掉任意的k个数字后,剩下的数字按照原次序排列得到的最小数。

    string removeKdigits(string num, int k) {
        vector<char> stk;
        for (auto& digit: num) {
            while (stk.size() > 0 && stk.back() > digit && k) {
                stk.pop_back();
                k -= 1;
            }
            stk.push_back(digit);
        }

        for (; k > 0; --k) {
            stk.pop_back();
        }

        string ans = "";
        bool isLeadingZero = true;
        for (auto& digit: stk) {
            if (isLeadingZero && digit == '0') {
                continue;
            }
            isLeadingZero = false;
            ans += digit;
        }
        return ans == "" ? "0" : ans;
    }

传纸条

vis[][];
hg[][];
dp[][][][];
int m,n;
void mymax(int i,int j,int k,int t){..}// 计算四个数中的最大值

void work(){
    for(int i=1;i<=m;i++){
        for(int j=1;j<=n;j++){
            for(int k=1;k<=m;k++){
                for(int t=1;t<=n;t++){
                    if((i!=k&&j!=l)||(i==k&&k==n)&&(j==l&&l==m)){
                         max=mymax(dp[i-1][j][k-1][t],dp[i-1][j][k][t-1],dp[i][j-1][k-1][t],dp[i][j-1][k][t-1])
                         dp[i][j][k][t]=max+hg[i][j]+hg[k][t];                       
                    }
                }
            }
		}
    }
}

序列划分

将一个包含n个正整数的序列划分成m个连续的子序列,假设第i个序列的各个数之和为S(i),求所有S(i)的最大值的最小值

#include 
int a[MAX];
bool judge(int x){
    int s=0,cnt=0;
    for(int i=0;i<n;i++){
        if(x<a[i])
            return false;
        if(s+a[i]<=x)
            s+=a[i];
        else{
            s=a[i];
            cnt++;
            if(cnt>m-1)
                return false;
        }
    }
    return true;
}
int solve(int l,int r){
    int m;
    while(l<r){
        mid=l+(r-l)/2;
        if(judge(mid))
            r=m;
        else 
            l=m+1
    }
    return l;
}
int main(){
  	int n,m;
   	cin>>n>>m;
    int max=0,sum=0;
	for(int i=0;i<n;i++){
        cin>>a[i];
        if(max<a[i])
            max=a[i];
        sum+=a[i];
    }
    cout<<solve(max,sum)<<endl;
}

分考场

n个人参加考试,任何两个认识的人不能分在同一个考场,则至少需要几个考场

#include 
#include 
#define N 301
#define min(a,b) a>b? b:a
int num=N,n;
int gxb[N][N]; //关系表
int p[N][N]; //房间状态
void dfs(int x,int k){// 同学的序号为x, 考场号为k
    if(k>num) return;//减枝
    if(x==n+1){ // 为n个同学分完考场
        num=min(num,k);
        return;
    }
   	for(int j=1;j<k;j++){//遍历房间
        int k=0;
        int flag=0;
        while(p[j][k]){
            k++;
            if(gxb[x][p[j][k]]){
                flag=1;
                break;
            }
		}
	   if(flag)
            continue;
        if(p[j][k]==0){
            p[j][k]=x;
            dfs(x+1,k);
            p[j][k]=0;//回溯
        }
        p[j][0]=x; //新增加房间
        dfs(x=1,k+1);
        p[j][0]=x;//回溯
    }
}
int main(){
    int m,s1,s2;
    memset(gxb,0,sizeof(gxb));
    memset(p,0,sizeof(p));
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++){
        scanf("%d%d",&s1,&s2);
        gxb[s1][s2]=gxb[s2][s1]=1;//建立关系的无向图
    } 
    dfs(1,1);
    printf("%d",&num);
    return 0;
}

lucky number

寻找质因数只有3,5,7 且小于等于x的lucky number个数。

#include 
#include 
#include 
using namespace std;
int main()
{
    vector<long long> lucky_nums = {1}; //因为后续要做乘法 所以第一个初始化为1
    long long x;
    cin >> x;
    int a, b, c;
    a = b = c = 0; //初始化
    while (1)
    {
        long long n3 = lucky_nums[a] * 3;  //不断的对容器里之前的每个数 做乘法(3 5 7)运算
        long long n5 = lucky_nums[b] * 5;//n3 n5 n7 分别记录着乘以 3 5 7 的结果
        long long n7 = lucky_nums[c] * 7;
        long long Min = min(min(n3, n5), n7);
        if (Min == n3)
            a++; //当前数值被记录,继续往后乘
        if (Min == n5)
            b++;
        if (Min == n7)
            c++;
        if (Min > x)
            break;                    //已找到解
        if (Min != lucky_nums.back()) //去重
            lucky_nums.push_back(Min);
    }
    cout << lucky_nums.size() - 1 << endl; //因为有1 的存在 所以记得 -1
    system("pause");
    return 0;
}

带行李

容量为L的行李箱,n个物品,已知每个物品的体积,求携带物体的方案数。(一个物体都不带也算一种)

#include 
using namespace std;
int L,n;
int v[20];
int num;//方案数
void dfs(int i, int w){// i物体的编号,当前已经装入物体的体积
	if(i==n){
		if(w<=L)
            num++;
        return;
    }
    dfs(i+1,w); // 第i个物品装入
    dfs(i+1,w+v[i]); // 第i个物品不装人
}
int main(){
    cin>>L>>n;
    for(int i=0;i<n;i++){
        cin>>v[i];
    }
    cout<<num<<endl;
}

选数

给定长度为n的数组和一个正整数k,问从数组中任选两个数使得和为k的倍数的选法有多少个?

#include 
using namespace std;
int n,k;
int nums[100];
int cnt;//选法
void dfs(int s){
    if(s==n){
		return;
    }
	for(int i=s+1;i<n;i++){
        if((nums[s]+nums[i])%k==0)
            cnt++;
        dfs(i);
    }
}
int main(){
    cin>>n>>k;
    for(int i=0;i<n;i++){
        cin>>nums[i];
    }  
    cnt=0;
    cout<<cnt<<endl;
}

欧几里得游戏

给定正整数M,N。 从Stan开始,从其中较大的一个数,减去较小的一个数的正整数倍,然后是Oline,直到一个人得到了0,他就取得了胜利。求谁会取得胜利呢?

#include 
using namespace std;
int main()
{
	int t , f ;
	cin >> t ;
	while(t --)
	{
		f = 1 ;
		long long a , b ;
		cin >> a >> b ;
		if(a < b) swap(a , b) ;
		while(b && a / b == 1 && a % b)
		 {
		 	f = - f ;
		 	long long t = a % b ;
		 	a = b , b = t ;
		 }
		 if(f == 1)
		  puts("Stan wins") ;
		 else puts("Ollie wins") ;
	}
	return 0 ;
}

野猫和胖子

一次,野猫遇到了一道有趣的几何游戏题目,便拿给胖子看。游戏要求在一个有 n个顶点凸多边形上进行,这个凸多边形的 n-3条对角线将多边形分成 n-2 个三角形,这 n-3条对角线在多边形的顶点相交。三角形中的一个被染成黑色,其余是白色。

双方轮流进行游戏,当轮到一方时,他必须沿着画好的对角线,从多边形上切下一个三角形。切下黑色三角形的一方获胜。胖子一看觉得确实很有趣,不如就一起玩玩吧。假设游戏由野猫先开始,那么野猫是否有必胜的策略呢?请写一个程序帮助野猫算一算。

#include
using namespace std;
int main()
{
    int n;
    cin>>n;
    if(n==5) //实质上是个打表
        cout<<"JMcat Win";
    else if(n%2==0)
            cout<<"JMcat Win"; 
                else cout<<"PZ Win";
    return 0;
}

晴天小猪

#include 
#include 
#include 
#include 
using namespace std;

const int maxn=1005;
int a[maxn][maxn];
int f[maxn][maxn];
int n;

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=i;j++)
        cin>>a[i][j];
    f[1][1]=a[1][1];//终点处就直接是该点时间
    for(int i=2;i<=n;i++)//一层一层往上推
    {
        for(int j=2;j<i;j++)//先求出从上一层推出来的最小值
            f[i][j]=min(f[i-1][j],f[i-1][j-1])+a[i][j];
        f[i][1]=min(f[i-1][1],f[i-1][i-1])+a[i][1];//特殊边界点处理
        f[i][i]=min(f[i-1][i-1],f[i-1][1])+a[i][i];//特殊边界点处理
        //同一层更新最优解
        for(int k=i-1;k>0;k--)//从右往左推 从右往左走的情况更新
            f[i][k]=min(f[i][k],f[i][k+1]+a[i][k]);
        f[i][i]=min(f[i][i],f[i][1]+a[i][i]);

        for(int l=2;l<=i;l++)//从左往右推 从左往右走的情况更新
            f[i][l]=min(f[i][l],f[i][l-1]+a[i][l]);
            f[i][1]=min(f[i][1],f[i][i]+a[i][1]);

        for(int k=i-1;k>0;k--)//再推一遍从右往左推 从右往左走的情况更新
            f[i][k]=min(f[i][k],f[i][k+1]+a[i][k]);
            f[i][i]=min(f[i][i],f[i][1]+a[i][i]);

        for(int l=2;l<=i;l++)//再推一遍从左往右推 从左往右走的情况更新
            f[i][l]=min(f[i][l],f[i][l-1]+a[i][l]);
                f[i][1]=min(f[i][1],f[i][i]+a[i][1]);
    }
    cout<<f[n][1]<<endl;
}

顺治滑雪

    #include
    #include
    using namespace std;
    int g[600][600],l[600][600];
    int r,c;
    int ans=0;
    void dfs(int x,int y,int d)
    {
        if(d<=l[x][y])return;
        l[x][y]=d;
        if(d>ans)ans=d;
        if(g[x][y]>g[x-1][y])dfs(x-1,y,d+1);
        if(g[x][y]>g[x][y-1])dfs(x,y-1,d+1);
        if(g[x][y]>g[x+1][y])dfs(x+1,y,d+1);
        if(g[x][y]>g[x][y+1])dfs(x,y+1,d+1);
    }
    int main()
    {
        cin>>r>>c;
        memset(g,127/2,sizeof(g));
        for(int i=1;i<=r;i++)
            for(int j=1;j<=c;j++)
                cin>>g[i][j];
        memset(l,0,sizeof(l));
        for(int i=1;i<=r;i++)
            for(int j=1;j<=c;j++)
                dfs(i,j,1);
        cout<<ans;
        return 0;
    }

磊城堡

P1504

# include
# include
using namespace std;
int n,maxn,x,g,sum;
int a[1001],ans[100001];
bool f[100001];
int main()
{
    cin>>n;
    for(int k=1;k<=n;k++)
      {
          memset(f,0,sizeof(f));
        int g=0,sum=0;
          while(1)
          {
              cin>>x;
              if(x<0) break;
              a[++g]=x;
              sum+=x;
        }
        f[0]=1;
        a[0]=g;
        if(sum>maxn) maxn=sum;
        for(int i=1;i<=g;i++)
          {
              for(int j=sum;j>=a[i];j--)
              if(f[j-a[i]]&&!f[j])
              f[j]=1,ans[j]++;
          }
      }
    for(int i=maxn;i>=0;i--)
      {
        if(ans[i]==n)
        {
            cout<<i;
            return 0;
        }
      }
    cout<<0;
    return 0;
}

铺地毯

题目描述

为了准备一个独特的颁奖典礼,组织者在会场的一片矩形区域(可看做是平面直角坐标系的第一象限)铺上一些矩形地毯。一共有 n张地毯,编号从 1到 n。现在将这些地毯按照编号从小到大的顺序平行于坐标轴先后铺设,后铺的地毯覆盖在前面已经铺好的地毯之上。

地毯铺设完成后,组织者想知道覆盖地面某个点的最上面的那张地毯的编号。注意:在矩形地毯边界和四个顶点上的点也算被地毯覆盖。

输入格式

输入共 n + 2行。

第一行,一个整数n*,表示总共有 nn 张地毯。

接下来的 n行中,第 i+1行表示编号 i的地毯的信息,包含四个整数 a ,b ,g ,k每两个整数之间用一个空格隔开,分别表示铺设地毯的左下角的坐标 (a, b)以及地毯在 x轴和 y轴方向的长度。

第 n + 2行包含两个整数 x 和 y,表示所求的地面的点的坐标 (x, y)。

输出格式

输出共 1 行,一个整数,表示所求的地毯的编号;若此处没有被地毯覆盖则输出 -1

#include 
#include 
using namespace std;

bool judge(vector<int>v,int x,int y ) {
	if(x>=v[0]&&y>=v[1]&&x<=(v[0]+v[2])&&y<=(v[1]+v[3]))
		return true;
	else
		return false;
}

int main() {
	int n,x,y;
	cin >> n;
	vector<vector<int>>  v(n+1, vector<int>(4));
	for (int i = 1; i <=n; i++) {
		for (int j = 0; j < 4; j++)
			cin >> v[i][j];
	}
	cin >> x >> y;
	int res=-1;
	for (int i = n; i >= 1; i--) {
		if (judge(v[i], x, y))
		{
			res = i; break;
		}
	}
	cout << res;
}

回文数

#include 
#include 
#include 
#include 
using namespace std;


string add(string m,int n) {

	string r = string(m.rbegin(), m.rend());
	string res;
	int c = 0;
	for (int i = 0; i < m.length(); i++) {
		int temp = (m[i] - '0') + (r[i] - '0')+c;
		res+=(temp% n+'0');
		c = temp / n;
	}
	if(c!=0)
		res += (c + '0');
	return res;
}

int StringToHex(const string& s)
{
	istringstream i(s);
	int x;
	if (i >>hex>> x)
		return x;
	return -1; // if error	
}

string HexToString(int x)
{
	ostringstream o;
	if (o <<hex<< x)
		return  o.str();
	return "conversion error";
}

string addhex(string m) {
	string r = string(m.rbegin(), m.rend());
	int res = StringToHex(m) + StringToHex(r);
	return HexToString(res);
}
int main()
{
	int n;
	cin >> n;
	string m;
	cin >> m;
	string res;
	if (n == 16) {
		for (int i = 1; i <= 30; i++) {
			res = addhex(m);
			string rev = string(res.rbegin(), res.rend());
			if (res == rev)
			{
				cout <<"STEP="<< i;
				return 0;
			}
			else
				m = res;
		}
	}
	else {
		for (int i = 1; i <= 30; i++) {
			res = add(m, n);
			string rev = string(res.rbegin(), res.rend());
			if (res == rev)
			{
				cout << "STEP=" << i;
				return 0;
			}
			else
				m = res;
		}
	}
	cout << "Impossible!" ;
}

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