SDU程序设计思维Week12-作业 区间DP/状压DP

SDU程序设计思维Week12-作业

A - 必做题 - 1

Description

给出n个数,zjm想找出出现至少(n+1)/2次的数, 现在需要你帮忙找出这个数是多少?
本题包含多组数据:
每组数据包含两行。
第一行一个数字N(1<=N<=999999) ,保证N为奇数。
第二行为N个用空格隔开的整数。
数据以EOF结束。
对于每一组数据,你需要输出你找到的唯一的数。

Sample

Input:
5
1 3 2 3 3
11
1 1 1 1 1 5 5 5 5 5 5
7
1 1 1 1 1 1 1
Output:
3
5
1

Idea

题意:给定n个数,找出出现至少(n+1)/2次的数,这个数必然是唯一的

用map存储这n个数,key对应这个数,value对应这个数出现的次数
遍历map中的数,找到出现至少(n+1)/2次的那个数。

Summary

这题比较简单,关键是map的一些操作

Codes

#include <iostream>
#include <map>
using namespace std;
int n;
map<int,int> m;

int main()
{
	while (scanf("%d",&n)!=EOF) {
		map<int, int> m;
		for (int i = 1; i <= n; i++) {
			int a;
			cin >> a;
			if (m.find(a)==m.end())m.insert({ a,1 });
			else m[a]++;
		}
		int pos=(n+1)/2;
		for (auto i = m.begin(); i != m.end(); i++) {
			if (i->second >= pos) { cout << i->first << endl; break; }

		}
	}
}

B - 必做题 - 2

Description

zjm被困在一个三维的空间中,现在要寻找最短路径逃生!
空间由立方体单位构成。
zjm每次向上下前后左右移动一个单位需要一分钟,且zjm不能对角线移动。
空间的四周封闭。zjm的目标是走到空间的出口。
是否存在逃出生天的可能性?如果存在,则需要多少时间?

输入第一行是一个数表示空间的数量。
每个空间的描述的第一行为L,R和C(皆不超过30)。
L表示空间的高度,R和C分别表示每层空间的行与列的大小。
随后L层,每层R行,每行C个字符。
每个字符表示空间的一个单元。’#‘表示不可通过单元,’.‘表示空白单元。
zjm的起始位置在’S’,出口为’E’。每层空间后都有一个空行。

L,R和C均为0时输入结束。
每个空间对应一行输出。
如果可以逃生,则输出如下
Escaped in x minute(s).
x为最短脱离时间。
如果无法逃生,则输出如下
Trapped!

Sample

Input:
3 4 5
S….
.###.
.##..
###.#

#####
#####
##.##
##…

#####
#####
#.###
####E

1 3 3
S##
#E#
###

0 0 0
Output:
Escaped in 11 minute(s).
Trapped!

Idea

题意:给定一个三维迷宫,要求从入口走到出口所需的最短时间。

  • 用三维数组记录迷宫信息,并标记入口
  • 自定义结构体pos,记录xyz坐标,以及当前移动的步数即时间
  • BFS
    将以起点为坐标,步数为0的pos压入队列,开始BFS搜索共6个方向,用vis记录一个点是否被遍历过,搜索时跳过超出三维空间或已经遍历到的点,将搜索到的点压入队列,重复操作。宽搜+避免重复遍历可以保证率先到达终点的那条路一定是最短的路。

Summary

这题是迷宫问题的变形,用BFS或DFS解题。由于题目要求出最短的时间即最短的一条路,所以用宽搜可以令各条路同步进行,率先完成的那条路就是最短的路。用深搜难以保证到达终点的那条路就是最短的路,而且容易爆栈。

Codes

#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
char a[40][40][40];
bool vis[40][40][40] = { false };
int L, R, C;
int sx, sy, sz;

int dx[] = { 0,0,0,0,1,-1 };
int dy[] = { 0,0,-1,1,0,0 };
int dz[] = { 1,-1,0,0,0,0 };

struct pos {
	int x, y, z, step;
	pos(){}
	pos(int a, int b, int c, int d) {
		x = a, y = b, z = c, step = d;
	}
};


void bfs()
{
	
	queue<pos> q;
	q.push(pos(sx,sy,sz,0));
	vis[sx][sy][sz] = 1;
	while (!q.empty())
	{
		pos p;
		p = q.front();
		q.pop();
		if (a[p.x][p.y][p.z] == 'E')
		{
			printf("Escaped in %d minute(s).\n", p.step);
			return;
		}
		for (int i = 0; i < 6; i++)
		{
			pos pp;
			pp.x = p.x + dx[i];
			pp.y = p.y + dy[i];
			pp.z = p.z + dz[i];
			pp.step = p.step;

			if (a[pp.x][pp.y][pp.z] != '#'&&pp.x >= 0 && pp.x < L&&pp.y >= 0 && pp.y < R&&pp.z >= 0 && pp.z < C &&!vis[pp.x][pp.y][pp.z])
			{
				vis[pp.x][pp.y][pp.z] = 1;
				pp.step++;
				q.push(pp);
			}
		}
	}
	printf("Trapped!\n");
}

void init() {

	for (int i = 0; i < L; i++)
		for (int j = 0; j < R; j++)
			for (int k = 0; k < C; k++)
				vis[i][j][k] = false;
}
int main()
{
	while (1) {
		cin >> L >> R >> C;
		if (L == 0 && R == 0 && C == 0)return 0;
		
		init();
		for (int i = 0; i < L; i++) {
			for (int j = 0; j < R; j++)
			{
				cin >> a[i][j];
				for (int k = 0; k < C; k++)
				{
					
					if (a[i][j][k] == 'S') { sx = i, sy = j, sz = k; }

				}
			}
			
		}
		//cout << sx << sy << sz << endl;
		bfs();


	}
}

C - 必做题 - 3

Description

东东每个学期都会去寝室接受扫楼的任务,并清点每个寝室的人数。
每个寝室里面有ai个人(1<=i<=n)。从第i到第j个宿舍一共有sum(i,j)=a[i]+…+a[j]个人
这让宿管阿姨非常开心,并且让东东扫楼m次,每一次数第i到第j个宿舍sum(i,j)
问题是要找到sum(i1, j1) + … + sum(im,jm)的最大值。且ix <= iy <=jx和ix <= jy <=jx的情况是不被允许的。也就是说m段都不能相交。
注:1 ≤ i ≤ n ≤ 1e6 , -32768 ≤ ai ≤ 32767 人数可以为负数。。。。(1<=n<=1000000)

输入m,输入n。后面跟着输入n个ai
输出最大和
数据量很大,需要scanf读入和dp处理。

Sample

Input:
1 3 1 2 3
2 6 -1 4 -2 3 -2 3
Output:
6
8

Idea

题意:有n个宿舍每个宿舍有数个人(可为负数),可以扫楼m次,每次扫楼可以扫一段连续的宿舍获得这些宿舍的人,求出m次扫楼可以获得的最大人数,数据量大用动态规划处理

  • 定义状态f [i][j] 表示前i次扫楼扫到第j个宿舍可以获得的最大人数 i<=j<=n
  • 状态转移方程f[i][j]=max(f[i][j-1]+a[j],maxk+a[j]),maxk=max(f[i-1][k]) 即前i-1次清扫可以获得的最大人数
  • 由于n<=1e6,开不了这么大的二维数组,因此利用滚动数组优化
    用pre代替原先的max(f[i-1][k]) 记录前i-1段中的最大人数,并在每层更新当前的最大人数

Summary

这题是动态规划的应用。状态转移方程有点难想,f[i][j]=max(f[i][j-1]+a[j],max(f[i-1][k])+a[j])
同时也要考虑大数据量,只能用滚动数组处理

Codes

#include <iostream>
#include <algorithm>
using namespace std;
int m, n;
int a[1000020],f[1000020],pre[1000020];
int main()
{
	while (scanf("%d%d",&m,&n)!=EOF) {
		memset(f, 0, sizeof(f));
		memset(pre, 0, sizeof(pre));
		for (int i = 1; i <= n; i++)
			scanf("%d", &a[i]);
		int maxk;
		for (int i = 1; i <= m; i++) {
			maxk = INT_MIN;
			for (int j = i; j <= n; j++) {
				f[j] = max(f[j - 1], pre[j - 1]) + a[j];
				pre[j - 1] = maxk;
				maxk = max(maxk, f[j]);
			}
		}
		printf("%d\n", maxk);
	}
}

D - 选做题 - 1

Description

We give the following inductive definition of a “regular brackets” sequence:

    1.the empty sequence is a regular brackets sequence,
    2.if s is a regular brackets sequence, then (s) and [s] are regular brackets sequences, and
    3.if a and b are regular brackets sequences, then ab is a regular brackets sequence.
    4.no other sequence is a regular brackets sequence

For instance, all of the following character sequences are regular brackets sequences:

    (), [], (()), ()[], ()[()]

while the following character sequences are not:

    (, ], )(, ([)], ([(]

Given a brackets sequence of characters a1a2 … an, your goal is to find the length of the longest regular brackets sequence that is a subsequence of s. That is, you wish to find the largest m such that for indices i1, i2, …, im where 1 ≤ i1 < i2 < … < im ≤ n, ai1ai2 … aim is a regular brackets sequence.

Given the initial sequence ([([]])], the longest regular brackets subsequence is [([])].

The input test file will contain multiple test cases. Each input test case consists of a single line containing only the characters (, ), [, and ]; each input test will have length between 1 and 100, inclusive. The end-of-file is marked by a line containing the word “end” and should not be processed.
For each input case, the program should print the length of the longest possible regular brackets subsequence on a single line.

Sample

Input:
((()))
()()()
([]])
)[)(
([][][)
end
Output:
6
6
4
0
6

Idea

题意:给定一个括号序列,找到最长的合法子序列的长度,子序列与子串不同之处在于可以不是连续的。

  • 首先定义f[i][j]表示整个括号序列的第 i 位至第 j 位中最长合法子序列的长度
  • 初始化f[i][j]=0,满足空序列为合法子序列
  • 如果A、B分别是合法子序列,则AB为合法子序列
    f[i][j] = max(f[i][j], f[i][k] + f[k + 1][j])
  • 如果当前子序列的左右两端括号匹配,则最长合法子序列的长度加2
    f[i][j] = max(f[i][j], f[i + 1][j - 1] + 2)
  • 按照以上规则,从序列尾部开始向头部移动子序列的左边界,不断更新当前子序列中的最长合法子序列长度,最终答案为f[0][n-1]

Summary

这题是动态规划的应用,状态转移方程根据题目中的合法子序列的两个要求构造
分别有f[i][j] = max(f[i][j], f[i][k] + f[k + 1][j]),f[i][j] = max(f[i][j], f[i + 1][j - 1] + 2)
每次改变左边界i时都要重新更新当前i至序列尾部中所有子串中最长的合法子序列长度

Codes

#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
string str;
int f[110][110];

int main()
{
	while (1) {
		cin >> str;
		if (str == "end")break;
		int n = str.size();

		memset(f, 0, sizeof(f));
		for(int i=n-1;i>=0;i--)
			for (int j = i + 1; j < n; j++) {
				for (int k = i; k < j; k++)
					f[i][j] = max(f[i][j], f[i][k] + f[k + 1][j]);
				if ((str[i] == '('&&str[j] == ')') || (str[i] == '['&&str[j] == ']'))
					f[i][j] = max(f[i][j], f[i + 1][j - 1] + 2);
			}
		printf("%d\n", f[0][n - 1]);



	}


}

E - 选做题 - 2

Description

马上假期就要结束了,zjm还有 n 个作业,完成某个作业需要一定的时间,而且每个作业有一个截止时间,若超过截止时间,一天就要扣一分。
zjm想知道如何安排做作业,使得扣的分数最少。
Tips: 如果开始做某个作业,就必须把这个作业做完了,才能做下一个作业。

有多组测试数据。第一行一个整数表示测试数据的组数
第一行一个整数 n(1<=n<=15)
接下来n行,每行一个字符串(长度不超过100) S 表示任务的名称和两个整数 D 和 C,分别表示任务的截止时间和完成任务需要的天数。
这 n 个任务是按照字符串的字典序从小到大给出。

每组测试数据,输出最少扣的分数,并输出完成作业的方案,如果有多个方案,输出字典序最小的一个。

Sample

Input:
2
3
Computer 3 3
English 20 1
Math 3 2
3
Computer 3 3
English 6 3
Math 6 3
Output:
2
Computer
Math
English
3
Computer
English
Math

Idea

题意:有n个作业,每个作业有完成时间与ddl,需在ddl前完成作业,超过ddl一天扣一分,要求出最少扣分且字典序最小的方案

  • 自定义结构体task存储作业的名字、ddl、完成时间

  • 要求输出字典序最小的方案,首先对这n个作业按字典序排序

  • 定义状态S是完成作业的集合,f[S]表示完成S作业集合后被扣的最少分数
    将S转换成二进制,即共有n位,每个作业对应一位,由于排序,字典序小的作业对应低位

  • 状态转移方程:f[S|(1<<X)]=f[S]+max(sum+c[x]-d[x],0)
    X表示当前欲完成的作业,如果X已经包含在S中则忽略
    S|(1<<X)即把X添加到S集合中,sum表示完成S作业集合对应的总时间,c[x]表示X的完成时间,d[x]表示X的ddl,max(sum+c[x]-d[x],0)表示作业X被扣的分数

  • 枚举
    S从小到大枚举,X从小到大枚举,对于每个S集合实时计算完成它的时间sum
    由于需要回溯输出方案,用pre记录每个状态S对应的最后一个作业
    最后的f[S]就是最少扣分数,而最佳方案存储于数组逆向输出即可。

Summary

这题方法是动态规划+位运算,每个作业对应状态S中的一位,1表示完成了该作业,0表示未完成该作业,状态转移方程是f[S|(1<<X)]=f[S]+max(sum+c[x]-d[x],0)
需要注意的坑:
①枚举完所有S后,S处于10000…的状态,需要-1恢复到11111…,对应的f[S]才是正确答案
②对于每个状态S,要分别计算它的完成时间S
③由于作业序号从1开始,用到X进行位运算时需要写成1<<(X-1)

Codes

#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
using namespace std;
int _,n;
struct task {
	string name;
	int ddl;
	int days;
	task(){}
	task(string s, int d, int c) {
		name = s, ddl = d, days = c;
	}
	bool operator<(task tt) {
		return name < tt.name;
	}

}t[20];
int f[1 << 15 + 1], pre[1 << 15 + 1];

int main()
{
	cin >> _;
	while (_--)
	{
		cin >> n;
		for (int i = 1; i <= n; i++) {
			cin >> t[i].name >> t[i].ddl >> t[i].days;
		}

		sort(t + 1, t + 1 + n);

		int s = 0;
		for (int i = 0; i <= (1<<n)-1; i++)pre[i] = 0,f[i]=1e8;
		f[0] = 0;
		for (s = 0; s <= (1 << n) - 1; s++) {
			int sum = 0;
			for (int i = 1; i <= n; i++) {
				if (s&(1 << (i-1)))sum += t[i].days;
			}

			for (int i = 1; i <= n; i++) {
				
				if (s&(1 << (i - 1)))continue;
				
				int tmp = max(sum + t[i].days - t[i].ddl, 0);
				if (f[s] + tmp < f[s | (1 << (i - 1))]) {
					f[s | (1 << (i - 1))] = f[s] + tmp;
					//int sm = sum[s];
					pre[s | (1 << (i - 1))] = s;
					
				}
			}
		}
		s--;
		printf("%d\n", f[s]);
		vector<int> v;

		while (s != 0)
		{
			int x = s - pre[s];
			x = log2(x) + 1;
			v.push_back(x);
			s = pre[s];
		}
		for (int i = v.size() - 1; i >= 0; i--)
			cout << t[v[i]].name << endl;
		

	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值