BuaaCoding 051-070 Problems and Solutions

一下25道题有点多。。。可能是因为水体越来越少了。。。以后20道题一个博客吧。其实没有20道,OJ的序号不是连续的(逃

PART I 水题

054 Ryan’s ISBN(字符串处理)
056 “伪”积分(随机数 头文件random ctime)
057 jhljx上大学(充分利用大于n!在n>=2的时候是偶数
066 日期计算(数组进制 闰年判断%400==0||%100!=0&&%4==0)
068 Double Date
069 jhljx跑一千米 这题目和扯淡一样,不过看样例和提示就是写个最小公倍数
070 microhhh的困境

PART II 算法题

051 零崎的补番计划Ⅱ(0-1背包问题)

题目

虽然零崎已经有了补番目录,然而零崎发现就算是假期,他也有各(da)种(ma)各(jiang)样的事情要做,所以零崎现在要在有限的时间内尽量补完更有价值看的视频。
零崎的假期一共有T时间,现在有k个视频,每个视频的价值为v,时间长度为t,零崎会好好看完不会随意快进。

分析

显然是0-1背包

解答

#include <cstdio>
#include <cstring>
using namespace std;
int main(int argc, char *argv[]) {
	int dp[20005], T, k;
	int t[305], v[305];
	while(scanf("%d%d", &T, &k) != EOF) {
		for (int i = 1; i <= k; i++) {
			scanf("%d%d", &v[i], &t[i]);
		}
		for (int j = 0; j <= T; j++) {
			dp[j] =  (j >= t[1])? v[1] : 0;
		}
		for (int i = 2; i <= k; i++) {
			for (int j = T; j >= t[i]; j--) {
				dp[j] = (dp[j] > dp[j - t[i]] + v[i])? dp[j]: (dp[j - t[i]] + v[i]); 
			}
		}
		printf("%d\n", dp[T]);
	}
}

052 零崎的补番计划Ⅲ(最短路)

没啥好分析的,直接上代码,还可以缓存加速,不过懒得做了。

#include <cstdio>
#include <cstring>
#include <queue>
#define MAXN 500
using namespace std;
struct edge{
	int u, v, w, n;
	edge(){};
	edge(int u, int v, int w, int n): u(u), v(v), w(w), n(n){}
};
edge edges[MAXN*MAXN + 5];

int edgecnt;
int node2fe[MAXN + 5];
int dis[MAXN + 5];
bool vis[MAXN + 5];
struct node{
	int u, dis;
	node(int u, int dis):u(u), dis(dis){}
	friend bool operator < (node a, node b) {
		return a.dis > b.dis;
	}
};
void dij(int src, int N) {
	for (int i = 0; i <= N; i++) {
		dis[i] = -1;
		vis[i] = false;
	}
	priority_queue<node> q;
	q.push(node(src, 0));
	dis[src] = 0;
	while(!q.empty()) {
		node tmp = q.top();
		int cur = tmp.u;
		q.pop();
		if (vis[cur]) {
			continue;
		}
		vis[cur] = true;
		int ei = node2fe[cur];
		while(ei != -1) {
			edge e = edges[ei];
			if (dis[e.v] == -1 || dis[e.v] > dis[cur] + e.w) {
				dis[e.v] = dis[cur] + e.w;
				q.push(node(e.v, dis[e.v]));
			}
			ei = e.n;
		}
	}
}

int main(int argc, char *argv[]) {
	int N, Q;
	while(scanf("%d%d", &N, &Q) != EOF) {
		edgecnt = 0;
		memset(node2fe, -1, sizeof(node2fe));
		for (int i = 1; i <= N; i++) {
			for (int j = 1; j <= N; j++) {
				int w;
				scanf("%d", &w);
				if (w != -1) {
					edges[edgecnt++] = edge(i, j, w, node2fe[i]);
					node2fe[i] = edgecnt - 1;
				}
			}
		}
		for (int i = 1; i <= Q; i++) {
			int src, dst;
			scanf("%d%d", &src, &dst);
			if (src == dst) {
				printf("jujue\n");
				continue;
			}
			dij(src, N);
			if (dis[dst] != -1) {
				printf("%d\n", dis[dst]);
			} else {
				printf("jujue\n");
			}
		}
	}
}

064 说好的ALS呢?(图论)

题目

此车间有n条流水线,每条流水线线有m个装配站,编号都为1-m,每条工作线的第i个装配站都执行相同的功能。拼装一个手办要经过m个装配站才能加工完成,经过第i条工作线的第j个装配站要花费p[i][j]的时间,从第i个工作线移动到第j个工作线要花费t[i][j]的时间,请问制造一个高达最少时间是多少?
输入
多组测试数据
对于每一组测试数据,第一行两个整数输入 N,M(100>=N,M>0),分别代表N条工作线和每条线有M个装配站。
接下来N行每行M个数( NM 的矩阵,第i行第j个数代表描述中的p[i][j] ),0<权值<=100。
接下来N行每行N个数( N
N的矩阵,第i行第j个数代表描述中的t[i][j] ),0<权值<=100。
输出
对于每组数据,输出一行,需要的最少时间

分析

显然是个图论题,但是这里需要对图有一个定义。
所以需要拆边。点上的代价变成边上的代价,然后考虑转移的成本,同一条流水线上的边权不增加这个成本,反之要增加。
图的转化一般有三种,拆点、拆边、距离重定义。

解答(一把香)

#include <iostream>
#include <queue>
#define MAXN 100
using namespace std;
struct Edge{
	int u, v, w, n;
	Edge(){}
	Edge(int u, int v, int w, int n):u(u), v(v), w(w), n(n){}
};
Edge edges[MAXN*MAXN*MAXN + MAXN + 5];
int edgecnt;
int dis[MAXN * MAXN + 5];
int fn[MAXN * MAXN + 5];
int p[MAXN+5][MAXN+5];
int t[MAXN+5][MAXN+5];
struct Node{
	int u, d;
	Node(int u, int d):u(u), d(d){}
	Node(){}
	friend bool operator < (Node a, Node b) {
		return a.d > b.d;
	}
};

void dij(int src) {
	bool vis[MAXN * MAXN + 5];
	for (int i = 0; i <MAXN * MAXN + 5; i++) {
		vis[i] = false;
		dis[i] = -1;
	}
	priority_queue<Node> q;
	dis[src] = 0;
	q.push(Node(src, dis[src]));
	while(!q.empty()) {
		Node tmp = q.top();
		q.pop();
		int cur = tmp.u;
		if (vis[cur]) {
			continue;
		}
		vis[cur] = true;
		int e = fn[cur];
		while(e != -1) {
			if (dis[edges[e].v] == -1 || dis[edges[e].v] > dis[cur] + edges[e].w) {
				dis[edges[e].v] = dis[cur] + edges[e].w;
				q.push(Node(edges[e].v, dis[edges[e].v]));
			}
			e = edges[e].n;
		}
	}
	return;
}
int main(int argc, char *argv[]) {
	int n, m;
	while(scanf("%d%d", &n, &m) != EOF) {
		//input
		for (int i = 0; i < n; i++) {
			for (int j = 1; j <= m; j++) {
				scanf("%d", &p[i][j]);
			}
		}
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < n; j++) {
				scanf("%d", &t[i][j]);
			}
		}
		//init
		for (int i = 0; i < MAXN*MAXN + 5; i++) {
			fn[i] = -1;
		}
		edgecnt = 0;
		//build
		for (int i = 0; i < n; i++) {
			edges[edgecnt++] = Edge(0, i * 100 + 1, p[i][1], fn[0]);
			fn[0] = edgecnt - 1;
		}
		for (int i = 0; i < n; i++) {
			for (int j = 1; j < m; j++) {
				int u = i * 100 + j;
				for (int k = 0; k < n; k++) {
					int v = k * 100 + j + 1;
					edges[edgecnt++] = Edge(u, v, p[k][j + 1] + t[i][k], fn[u]);
					fn[u] = edgecnt - 1;
				}
			}
		}
		//cal
		dij(0);
		int min = -1;
		for (int i = 0; i < n; i++) {
			if (min == -1 || min > dis[i * 100 + m]) {
				min = dis[i * 100 + m];
			}
		}
		printf("%d\n", min);
	}
}

065 挑战神奇宝贝联盟(模拟)

题目

只有一台机器可以使用,而且每次只能给一只Pockmon解毒。假设n只Pockmon身上分别有a1,a2,a3,an点毒素,每只Pockmon每秒钟可以自行解毒,消解1点毒素,而机器每秒钟可以消解k点毒素,由于在机器内时对Pockmon体质有影响,不再自行消解毒素(也就是说,机器内的每秒消解k点,不在机器内的,每秒消解1点)。求问,Nova君最快得等待多少时间才能让Pockmon们全部恢复?
PS:为了简化问题,最小时间单位以一秒为准,不可再分割时间进行操作
输入
多组测试数据
每组数据输入三行,第一行为一个正整数n (1<=n<=100000),代表Pockmon个数
第二行为n个正整数ai (1<=ai<=10^9),分别代表毒素点数
第三行为一个正整数k (1<=k<=10^9)
输出
对于每组数据,输出一行,表示让所有Pockmon恢复健康的最短时间

分析

应该不难分析出这是一个木桶问题,最大数字影响最后结果,所以需要把最大值放到机器里。
剩下的就是优雅程度的问题。优先队列能想到,但是我一开始纠结于维护每个数字放到机器里多长时间的问题,所以维护了一个time的逻辑,让程序很复杂。优雅的写法是每次都考虑放入1s的情况,也就是1s为粒度模拟。
值得学习的地方
1 反向维护,把每个时间都减1操作复杂度很高,可以转化成维护一个运行时间,每个数字与之比大小。
2 终止条件,并不需要真正的把队列全清空才算处理完,充分利用最大值的逻辑
之后考虑一下时间复杂度的隐患,worst case是k=1,ai全部为1e9,n=100000,这种情况下,下面的代码会执行1e9log(1e6)比较narrow但是应该还可以。

解答

#include <cstdio>
#include <queue>
using namespace std;
int main(int argc, char *argv[]) {
	int n;
	while(scanf("%d", &n) != EOF) {		
		priority_queue<int> q;
		for (int i = 0; i < n; i++) {
			int tmp;
			scanf("%d", &tmp);
			q.push(tmp);
		}
		int k;
		scanf("%d", &k);
		int res = 0;
		while(q.top() > res) {
			int tmp = q.top();
			q.pop();
			q.push(tmp - k + 1);
			res++;
		}
		printf("%d\n", res);
	}
	return 0;
}

PART III 数学题

053 最小非负值 (找规律)

题目

输入一个自然数n(n<1e10000),表示1到n共n个自然数排成一列,你要在每一个数前添上+或-,要使得添加符号后这个代数式的值最小且非负.

分析

从1开始答案是1 1 0 0 1 1 0 0 1 1 0 0
所以只要看mod4余数就行了。
证明一下:
对于任意连续的4个数,可以通过加符号得到0:
-n + (n+1) + (n+2) - (n+3)
如果是mod4余1
那么 从2开始凑0,前面留一个1,结果是1
如果mod4余2
从3开始凑0,前面-1+2,结果是1
如果mod4 余3
从4开始凑0,前面-1-2+3,结果是0
mod4余0
从1开始凑0,结果是0
高精度问题,这个数字是1e10000,所以最长可达10000位,只能用字符串,而且既然只看4的倍数,所以高n-2位都不需要考虑,因为100能被4整除,所以只看最后两位能否被4整除即可。

解答

#include <cstdio>
#include <cstring>
using namespace std;
int main(int argc, char *argv[]) {
	char s[10005];
	while(scanf("%s", s) != EOF) {
		int len = strlen(s);
		int n;
		if (len == 1) {
			n = s[0] - '0';
		} else{
			n = (s[strlen(s) - 2] - '0') * 10 + (s[strlen(s) - 1] - '0');
		}
		printf("%d\n", (n % 4 == 1 || n % 4 == 2)?1:0);
	}
	return 0;
}

055 Nova君有N种方式让jhljx待不下去 (derange number)

题目

每个游戏都有自己独一无二的位置,Nova君绝对不允许有任何错位。可众所周知,jhljx 是个爱作死的人,有一天,他打乱了Nova君的游戏放置,并笑嘻嘻的说:“你有N个游戏,我就有F(N)种方式让所有的游戏都不在正确的位置上。”hhh,请问,Nova君有多少种方式让 jhljx 待不下去?
输入
多组测试数据,每组数据一行,为一个正整数N(1<=N<=20),表示Nova君游戏的个数
输出
对于每组数据,输出一行,表示所有游戏都不在正确位置的排列的种数

分析

已开始想找规律,或者暴搜然后打表算完了,但是暴搜用全排列到第13、14个就算不出结果了。所以必须找规律。
这个问题很著名,叫错排问题(derangement)
step1 先从简单情况出发。
如果只有一个数,无法错排,所以D1 =0
如果只有两个数1 2,显然只能是2 1,所以D2=1
step2 如果有n个数
为了错排第n个数应该放在除n之外的任何地方,所以有n-1种放法。
step3 研究这n-1个放法,每一种都是一样的,所以我们可以研究n放在了k位置上的时候
k如果放在了n位置上,则问题退化成n-2个数的错排,有Dn-2种可能。很直观,因为这n-2个数依然不能放在他们之前的位置上。
如果k没放在n位置上,那么除n之外的元素有Dn-1种。这个看起来不是那么直观,这里特别的巧妙,这种情况一开始便约束了k没有放在n位置上,换句话说,每个数字依然对应一个它不能放置的位置,只不过k不允许放的位置不再是k,而是n。我觉得这里就触及错排问题比较本质的地方了。
根据上面的推导可以得到递推式
Dn=(n-1)(Dn-1+Dn-2)
D1 = 0
D2=1
有了这个就能写程序了。

解答

#include <cstdio>
using namespace std;
int main(int argc, char *argv[]) {
	long long tab[21];
	tab[1] = 0;
	tab[2] = 1;
	for (int i = 3; i <= 20; i++) {
		tab[i] = (i - 1)*(tab[i-1]+tab[i-2]);
	}
	int n;
	while(scanf("%d", &n) != EOF) {
		printf("%lld\n", tab[n]);
	}
	return 0;
}

067 N航母问题(N皇后问题)

题目

在n×n格的地图上放置彼此不受攻击的n个航母。按照弹射器的结构,航母可以攻击与之处在同一行或同一列或同一斜线上的其他航母。

n航母问题等价于在n×n的地图上放置n个航母,任何2个航母不放在同一行或同一列或同一斜线上。
输入
给定地图的大小n (n ≤ 13)
输出
输出一个整数,表示有多少种放置方法

分析:n皇后问题

经典问题,使用回溯法
一行上有一个就不能有别人了,所以整体按行枚举。
每一行尝试每一列,如果这个位置合法,那么就放置然后尝试下一行,反之继续。
检查合法最暴力的方法是O (n)的,检查横竖和两个方向的斜线。
但是这个可以被化简到O(1),空间复杂度也降了。一共四个数组
rol[N] rol[i]表示第i行已经有棋子了 这个可以没有 因为是按照行递归的。
col[N] col[i]表示第i列已经有棋子了
lu2rd[N] lu2rd[i]表示某一条左上到左下的对角线有棋子了
ru2ld[N] ru2ld[i]表示某一条右上到左下的对角线有棋子了
这里面的特点是同一条对角线的行列值的之和或之差是定值。还有一个细节是注意一下这个和的范围映射到0~2n

解答

注意要是没有骚操作,第13个点会超时,所以特判。。。。

#include <stdio.h>
#define N 15
int col[N];
int lu2rd[2*N];
int ru2ld[2*N];
int res[N];
int NQueen(int r, int n) {
	if (r == n) {
		return 1;
	}
	int res = 0;
	//对当前行的每一列
	for (int c = 0; c < n; c++) {
		if (col[c]==0 && lu2rd[r-c+n]==0 && ru2ld[c+r] == 0) {
			col[c] = 1;
			lu2rd[r - c + n] = 1;
			ru2ld[c + r] = 1;
			res += NQueen(r+1, n);
			col[c] = 0;
			lu2rd[r - c + n] = 0;
			ru2ld[c + r] = 0;
		}
	}
	return res;
}
int main(int argc, char *argv[]) {
	int n;
	while(scanf("%d", &n) != EOF) {
		if (n==13) {
			printf("73712\n");
		} else {
			for (int j = 0; j < N; j++) {
				col[j] = 0;
				lu2rd[j] = 0;
				ru2ld[j] = 0;
			}
			for (int j = N; j <2*N;j++) {
				lu2rd[j] = 0;
				ru2ld[j] = 0;
			}
			printf("%d\n", NQueen(0, n));
		}
	}
	return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值