BuaaCoding 051-070 Problems and Solutions

    xiaoxiao2022-07-14  166

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

    BuaaCoding 051-070 Problems and Solutions

    PART I 水题PART II 算法题051 零崎的补番计划Ⅱ(0-1背包问题)题目分析解答 052 零崎的补番计划Ⅲ(最短路)064 说好的ALS呢?(图论)题目分析解答(一把香) 065 挑战神奇宝贝联盟(模拟)题目分析解答 PART III 数学题053 最小非负值 (找规律)题目分析解答 055 Nova君有N种方式让jhljx待不下去 (derange number)题目分析解答 067 N航母问题(N皇后问题)题目分析:n皇后问题解答

    PART I 水题

    054 Ryan’s ISBN(字符串处理) 056 “伪”积分(随机数 头文件random ctime) 057 jhljx上大学(充分利用大于n!在n>=2的时候是偶数) 066 日期计算(数组进制 闰年判断@0==0||0!=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; }
    最新回复(0)