程序设计思维与实践 Week3 作业

A - 选数问题

给定n个正数,ZJM可以精确地选择和为S的K个数。现在ZJM想知道有多少方法可以得到它!
Input
第一行是整数T≤100,表示测试用例的数量。每种情况都有两行。第一行,三个整数表示n、K和S。第二行,n个正数。
Output
对于每种情况,一个整数在独立的行中表示答案。
Example Input
1
10 3 10
1 2 3 4 5 6 7 8 9 10
Example Output
4
Note
记住,k<=n<=16,所有数字都可以存储在32位整数中
思路
首先将这n个数放入数组中,以方便选取。
每个数都有两种选择,要么选择这个数,要么不选这个数。
一旦选取一个数,选择的数的个数就要加一,每次选取一个数后,需要判断此时选取的几个数的和是否等于S并且此时选取的数的个数是否为K,只有当同时满足这两个条件时,总计数加一。
直至所有的数都经历了选取与不选取(即对这n个数进行了一次排列组合)。
综上考虑,可以采用递归方式。
代码

#include<iostream>
using namespace std;


int N, K, S;
int Num;
int* a;

void solve(int i, int sum, int number)
{

	if (sum == S && number == K)//得到K个数的和为S
	{
		Num++;
		return;
	}
	if (i >= N)//调用的数的数量超过N
	{
		return;
	}
	if (number > K||sum > S )//如果相加的数的数量超过K,或者几个数的和超过S
	{
		return;
	}


	solve(i + 1, sum + a[i], number + 1);//选
	solve(i + 1, sum, number);//不选
	

}


int main()
{
	int T;
	cin >> T;
	for (int i = 0; i < T; i++)
	{
		Num = 0;
		cin >> N >> K >> S;
		a = new int[N];
		for (int j = 0; j < N; j++)
		{
			cin >> a[j];
		}
		solve(0, 0, 0);
		cout << Num << endl;
	}
}

B - 区间选点

数轴上有 n 个闭区间 [a_i, b_i]。取尽量少的点,使得每个区间内都至少有一个点(不同区间内含的点可以是同一个)
Input
第一行1个整数N(N<=100)
第2~N+1行,每行两个整数a,b(a,b<=100)
Output
一个整数,代表选点的数目
Example Input
2
1 5
4 6
Example Output
1
Example Input
3
1 3
2 5
4 6
Example Output
2
思路
将区间进行排序,因为要选取最少的点,因此考虑区间尽量右对齐,即按区间尾端端点升序、起端端点降序进行排列。
排序结果类似下图所示:
在这里插入图片描述
每次都选取当前区间的尾端端点。
因此,只有当下一个区间A的起端端点大于选取的区间的尾端端点时,可判断这个A区间内没有包含选择过的顶点,所以下一次选取这个A区间的尾端。
递归进行,直至所有的区间都包含有选取的顶点。

代码

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


struct section
{
	int a_i;
	int b_i;
};

int Num;//记录选取的点的数量
int N;//区间数量
section* sec;//区间集合

bool cmp(section& a, section& b)//区间排序
{
	if (a.b_i != b.b_i)return a.b_i < b.b_i;
	return a.a_i > b.a_i;
}

void FetchPoint(int current)//在当前区间内选点
{
	Num++;//每一次都选取当前区间的尾端端点
	int i = current;
	for (; i < N; i++)
	{
		if (sec[i].a_i > sec[current].b_i)
		{//有区间的开端大于当前区间的尾端
			//即当前选取的所有点都不在第i个区间内
			break;
		}
	}

	if (i >= N)//如果超出区间集合的最大值
	{
		return;
	}

	FetchPoint(i);
}

int main()
{
	Num = 0;
	cin >> N;
	sec = new section[N];
	for (int i = 0; i < N; i++)
	{
		cin >> sec[i].a_i >> sec[i].b_i;
	}

	sort(sec, sec + N, cmp);//区间排序
	FetchPoint(0);
	cout << Num << endl;
}

C - 区间覆盖

数轴上有 n (1<=n<=25000)个闭区间 [ai, bi],选择尽量少的区间覆盖一条指定线段 [1, t]( 1<=t<=1,000,000)。
覆盖整点,即(1,2)+(3,4)可以覆盖(1,4)。
不可能办到输出-1
输入
第一行:N和T
第二行至N+1行: 每一行一个闭区间。
输出
选择的区间的数目,不可能办到输出-1
样例输入
3 10
1 7
3 6
6 10
样例输出
2
思路
类似于第二题,预处理:剪切区间,即切掉不在[1,T]内的其他地方。
先将各个区间排序,此时的排序规则为:按照区间的起端端点升序排序,如果起端端点相同则按照尾端端点降序排序(即按照区间的长度进行降序排序,长区间在前)。
排好序后,将区间集合进行缩减,如果有区间被长区间完全覆盖,则删除该区间。
区间被覆盖的情况如下:

  1. 区间的起端端点相同时,第一个区间完全覆盖后面的与其起端端点相同的区间。可保留第一个区间,其余区间删除。
  2. 有一个区间A,与前面留下的最后一个区间B,A的起端端点大于B的起端端点,同时A的尾端端点小于等于B的尾端端点。

区间集合进行删除后,进行选择区间,第一个选择的是区间集合中的第一个区间。
如果该区间的起端端点不为1,则直接判断不能满足题意条件,退出程序,输出-1;
否则,就从当前选取的区间A所在的位置,依次遍历区间集合。对所遇到的区间情况进行标记。起始标记为0;
如果遇到区间B的起端端点>A的起端端点+1,直接退出循环。
如果遇到区间B的起端端点=A的起端端点+1,标记为1,退出循环。
如果遇到区间B的起端端点>A的起端端点,且B的起端端点<A的尾端端点,且B的尾端端点>A的尾端端点(即B区间有一小段与A区间重合),记下该区间,并标记为2.
退出循坏后,根据标记选择下一个选择的区间,进行新一轮的递归。

代码

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

struct section
{
	int ai;
	int bi;

	section& operator=(const section& rv)
	{
		ai = rv.ai;
		bi = rv.bi;
		return *this;
	}
};

int N;//区间数量
section* sec;//区间集合
int T;//线段尾端
int Num = 0;//记录选取区间数

bool cmp(section& a, section& b)//排序方式
{
	if (a.ai != b.ai)return a.ai < b.ai;
	return a.bi > b.bi;
}

void Clear()
{
	section* s = new section[N];
	int n = N;
	section temp = sec[0];
	s[0] = sec[0];
	int r = 1;
	for (int i = 1; i < N; i++)
	{
		if (sec[i].ai > T)
		{
			break;
		}

		if (sec[i].ai == temp.ai || sec[i].bi <= temp.bi)
		{
			continue;
		}
		else
		{
			s[r] = sec[i];
			temp = sec[i];
			r++;
		}
	}
	N = r;
	for (int i = 1; i < r; i++)
	{
		sec[i] = s[i];
	}
}




void FetchSection(int current)
{
	if (sec[current].bi == T)
	{
		//cout << "TTTT" << endl;
		Num++;
		return;
	}
	if (current >= N)
	{
		return;
	}

	if (current == 0)
	{
		if (sec[current].ai != 1)
		{
            Num = -1;
			return;
		}
		
	}

	Num++;
	int i = current;
	section temp = sec[current];
	int index = 0;
	int flag = 0;
	for (; i < N; i++)
	{
		if (sec[i].ai > sec[current].bi + 1)
		{
			break;
		}

		if (sec[i].ai == sec[current].bi + 1)
		{
			flag = 1;
			break;
		}

		if (sec[i].ai > temp.ai&& sec[i].bi>temp.bi&&sec[i].ai<=temp.bi  )
		{
			
			temp = sec[i];
			flag = 2;
			index = i;
		}

		
		
	}


	if (flag == 0)
	{
		//cout << "-1-1-1" << endl;
		Num = -1;
	}
	if (flag == 1)
	{
		//cout << "1111" << endl;
		FetchSection(i);
	}
	if (flag == 2)
	{
		//cout << "22222" << endl;
		FetchSection(index);
	}

}


int main()
{
	cin >> N >> T;
	sec = new section[N];
	for (int i = 0; i < N; i++)
	{
		cin >> sec[i].ai >> sec[i].bi;
		
		//剪切区间
		if (sec[i].ai < 1)
		{
			sec[i].ai = 1;
		}

		if (sec[i].bi > T)
		{
			sec[i].bi = T;
		}

	}
	sort(sec, sec + N, cmp);
	Clear();
	FetchSection(0);
	cout << Num << endl;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值