2014 Multi-University Training Contest 10 部分题目解题报告
Source:
这场挺坑的,题面很糟糕,admin也不给力= =。。听说这套题是去年BJTU出的,出题人已经退役,所以admin不明来源(所以很逗。。)
HDOJ 4971 A simple brute force problem.
题意:n个任务,m个技术难题,每个任务有获利,每个难题有解决的费用,有的任务需要解决一些难题才能做,难题之间会有依赖关系(实际的依赖关系和题面是反的)。选一些任务,求最大获利。
分析:20个任务,可以直接枚举,但是case有100组。这题数据比较水,所以实际上枚举+剪枝就可以过。正解自然是网络流。这一块还掌握的不行,这题大概是一个最小割和权闭合图的模型,源点连边到任务,边权为获利,难题连边到汇点,边权为费用,每个任务连权无穷的边到需要解决的问题,同时依赖的问题也要连上(任务1需要难题1,难题1依赖于难题2,那么任务1需要连边到难题1和2),跑一遍最大流,总获利减去最大流就是答案。待补。
HDOJ 4972 A simple dynamic programming problem
题意:两队打篮球赛,每次进球后只记录分差,问最终比分可能的情况数。(题面又坑爹了=。=)
分析:题面坑爹导致思路各种跑偏。由于我们知道最后的分差,所以可以只考虑两队总得分的种数。思考后发现我们不确定的情况只有两种,分差从1到2以及从2到1,总分可能+1或+3,遇上这种情况可能的总分就多一种,记2到1和1到2的总数为cnt,最后如果分差为0答案就是cnt+1,否则是2cnt+2(A队高分或A队低分)。然后还要注意各种不合法情况,就是相邻分差大于3,以及分差不变(除了1)。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 6 int T, n; 7 int main() 8 { 9 scanf("%d", &T); 10 int cas = 0; 11 while(T--) 12 { 13 scanf("%d", &n); 14 bool flag = false; 15 int p = 0, t = 0, cnt = 0; 16 for (int i = 0; i < n; i++){ 17 p = t; 18 scanf("%d", &t); 19 int del = abs(p-t); 20 if ((del > 3) || (del == 0 && p != 1)) flag = true; 21 if ((p == 1 && t == 2) || (p == 2 && t == 1)) cnt ++; 22 } 23 int ans = cnt; 24 if (t == 0) ans = cnt + 1; 25 else ans = 2 * cnt + 2; 26 if (flag) ans = 0; 27 printf("Case #%d: %d\n", ++cas, ans); 28 } 29 return 0; 30 }
HDOJ 4973 A simple simulation problem.
题意:开始1-n的排列,m次操作,操作D将[l, r]区间翻倍,操作Q查询[l, r]的众数出现次数。
分析:因为保证了[l, r]的长度小于10^8,所以可做了。大概就是用线段树之类的数据结构来维护,待补。
HDOJ 4974 A simple water problem
题意:一次match可能给两个选手最多加1分,现在给出n个选手的分数,问至少要多少场match。
分析:贪心考虑,每次肯定给两人都加分,也就是两两配对地打,所以是分数和/2(上取整)。还要考虑分数最大的那个人,如果他大于总和的一半,那么就让他和其他所有人配对就可以了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 6 int T, n, t, maxn; 7 long long sum; 8 int main() 9 { 10 scanf("%d", &T); 11 int cas = 0; 12 while(T--) 13 { 14 sum = maxn = 0; 15 scanf("%d", &n); 16 for (int i = 0; i < n; i++){ 17 scanf("%d", &t); 18 sum = sum + t; 19 maxn = max(maxn, t); 20 } 21 sum = (sum+1)/2; 22 long long ans; 23 if (maxn >= sum) ans = maxn; 24 else ans = sum; 25 printf("Case #%d: %lld\n", ++cas, ans); 26 } 27 return 0; 28 }
HDOJ 4975 A simple Gaussian elimination problem.
题意:方格填数,每个位置可以填0-9,告诉你每行和以及每列和,问是唯一解、无解还是多解。
分析:之前一次多校原题。由于这套题去年出的,所以就。做法一样,最大流,源点和行编号连边,汇点和列编号连边,分别是该行的和与该列的和,每行和每列连边,边权是9。跑一遍最大流,行和等于列和且满流有解。然后9减去残余网络上i行连j列的边的边权即是i行j列所填的数。如果能找到一个小矩形,使得顶点不是0和9与9和0,就可以多解。对应到残余网络中就是有一个环,可以调整流量。然而这题卡找环,找的算法不好就会T。标程很快,但是实际上标程对点标号1、2的做法是错误的。
我原来找环是这样的,每次枚举起点,在当前链上的点标记visiting,如果当前dfs的点能走到一个visiting的点,且当前点不是从与该点相连的边走过来的,说明形成了环。然后不管是哪个起点,一条边显然只要走一次,所以可以对边标记是否走过。按理来说复杂度是o(n+m)的。但是因为这题图是相当稠密的,所以即使每条边你走过了,你还是把这些边访问了一遍,才发现走过。可以说是o(nm)的。
一种解决的办法是双向链表,走过的边把它删了。。还有求强连通分量的,看有没有两行在一个强连通分量里,应该也是正确的,不过按理来说复杂度和dfs一样,o(n+m)但是还是要访问标记过的边。我觉得不同就在于tarjan只会dfs那些没被染色为某个scc的点,而dfs会枚举所有点作为起点。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 //能过本题,但是找环方法有误,标记为2的点有可能是环上的点。 2 /* 3 1 4 2 3 5 16 21 6 18 13 6 7 可以填 8 9 6 1 9 9 7 5 10 应为多解。 11 */ 12 #include<stdio.h> 13 #include<string.h> 14 15 int n, m, k, st, ed, esize, w, maxflow, cas; 16 int matrix[600][600]; 17 int lv[1200], en[1200], q[1200], cur[1200]; 18 int vis[600000]; 19 int vv[1200]; 20 struct Edge{ 21 int v, w, n; 22 } e[600000]; 23 void addedge(int u, int v, int w) 24 { 25 e[esize].v = v; e[esize].w = w; e[esize].n = en[u]; 26 en[u] = esize ++; 27 e[esize].v = u; e[esize].w = 0; e[esize].n = en[v]; 28 en[v] = esize ++; 29 } 30 bool bfs() 31 { 32 memset(lv, -1, sizeof(lv)); 33 int head, tail; 34 lv[st] = head = tail = 0; 35 q[tail++] = st; 36 while(head < tail) 37 { 38 int u = q[head++]; 39 for (int t = en[u]; t != -1; t = e[t].n){ 40 int v = e[t].v; 41 if (lv[v] == -1 && e[t].w > 0){ 42 lv[v] = lv[u] + 1; 43 q[tail++] = v; 44 if (v == ed) return 1; 45 } 46 } 47 } 48 return 0; 49 } 50 int dfs(int u, int maxf) 51 { 52 if (maxf == 0) return 0; 53 if (u == ed) return maxf; 54 int ans = 0, flow, tmp; 55 for (int &t = cur[u]; t != -1; t = e[t].n){ 56 int v = e[t].v; 57 if (e[t].w > 0 && lv[v] == lv[u] + 1){ 58 tmp = maxf - ans; 59 if (e[t].w < tmp) tmp = e[t].w; 60 flow = dfs(v, tmp); 61 if (flow > 0){ 62 e[t].w -= flow; 63 ans += flow; 64 e[t^1].w += flow; 65 if (maxf == ans) return ans; 66 } 67 else lv[v] = -10; 68 } 69 } 70 lv[u] = -10; 71 return ans; 72 } 73 bool findloop(int u, int fa) 74 { 75 if (vv[u] == 1) return true; 76 if (vv[u] == 2) return false; 77 vv[u] = 1; 78 for (int t = en[u]; t != -1; t = e[t].n){ 79 int v = e[t].v; 80 if (e[t].w <= 0 || v == fa || v == st || v == ed) continue; 81 if (vis[t] == cas) continue; 82 vis[t] = cas; 83 if (findloop(v, u)) return true; 84 } 85 vv[u] = 2; 86 return false; 87 } 88 bool find() 89 { 90 memset(vv, 0, sizeof(vv)); 91 for (int i = 1; i <= n; i++){ 92 if (!vv[i] && findloop(i, st)) return true; 93 } 94 return false; 95 } 96 int T; 97 int main() 98 { 99 memset(vis, 0, sizeof(vis)); 100 cas = 0; k = 9; 101 scanf("%d", &T); 102 while(T--) 103 { 104 printf("Case #%d: ", ++cas); 105 scanf("%d%d", &n, &m); 106 memset(en, -1, sizeof(en)); 107 esize = 0; 108 st = n+m+1, ed = n+m+2; 109 int sumr = 0, sumc = 0; 110 for (int i = 1; i <= n; i++){ 111 scanf("%d", &w); 112 addedge(st, i, w); 113 sumr += w; 114 } 115 for (int j = n+1; j <= n+m; j++){ 116 scanf("%d", &w); 117 addedge(j, ed, w); 118 sumc += w; 119 } 120 if (sumr != sumc){ 121 printf("So naive!\n"); 122 continue; 123 } 124 if (sumr == 0){ 125 printf("So simple!\n"); 126 continue; 127 } 128 for (int i = 1; i <= n; i++) 129 for (int j = n+1; j <= n+m; j++) 130 addedge(i, j, k); 131 maxflow = 0; 132 while(bfs()){ 133 for (int i = 1; i <= n+m+2; i++) cur[i] = en[i]; 134 maxflow += dfs(st, 7000000); 135 } 136 if (maxflow == sumr){ 137 if (find()) 138 printf("So young!\n"); 139 else{ 140 printf("So simple!\n"); 141 } 142 } 143 else{ 144 printf("So naive!\n"); 145 continue; 146 } 147 } 148 return 0; 149 }
HDOJ 4978 A simple probability problem.
题意:蒲丰投针。间距为D的平行线,一个直径为D的圆,圆内和圆上N个点,每两点连边,即针。随机放这个圆,问至少一根针与平行线相交概率。
分析:普通的蒲丰投针概率为2*l / pi*d,然后这题,思考一下发现,我们可以求一个凸包。里面的针是不用考虑的,如果和他们相交,肯定和凸包的边相交。每次和凸包相交,那么就是相交两条边(平行线交于线段端点和平行线与边重合的概率相对可以忽视)。和凸包相交的概率即是和任意两边相交的概率的和,即ΣΣPij。考虑和某两边相交的概率Pij。对于固定的边i,所有其他边j,即是和边i相交的概率Pi。所以就是ΣPi,然后ij可以互换,即Pij和Pji我们算了两次,所以还要除以2,最后就是ΣLi/pi*d,ΣLi即是凸包周长。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<cmath> 5 #include<algorithm> 6 #include<cstdlib> 7 #include<set> 8 #include<map> 9 #include<queue> 10 #include<ctime> 11 #include<string> 12 using namespace std; 13 14 const double pi = acos(-1.0); 15 int n; 16 double d; 17 const double eps = 1e-8; 18 inline int sign(double a){ 19 return a < -eps ? -1 : a > eps; 20 } 21 #define cross(p1, p2, p3) ((p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y)) 22 #define crossOp(p1, p2, p3) sign(cross(p1, p2, p3)) 23 struct point{ 24 double x, y, id; 25 point(){} 26 point(double _x, double _y): x(_x), y(_y){} 27 bool operator < (const point &p) const{ 28 int c = sign(x - p.x); 29 if (c) return c == -1; 30 return sign(y - p.y) == -1; 31 } 32 point operator -(const point &p) const{ 33 return point(x - p.x, y - p.y); 34 } 35 double dot(const point &p) const{ 36 return x * p.x + y * p.y; 37 } 38 }; 39 40 double dis(point a, point b) 41 { 42 return sqrt((a.x - b.x)*(a.x - b.x) + (a.y -b.y)*(a.y-b.y)); 43 } 44 int onSegment(point p, point q1, point q2) 45 { 46 return crossOp(q1, q2, p) == 0 && sign((p-q1).dot(p-q2)) <= 0; 47 } 48 vector<point> convexHull(vector<point> ps) 49 { 50 int n = ps.size(); 51 if (n <= 1) 52 return ps; 53 sort(ps.begin(), ps.end()); 54 vector<point> qs; 55 for (int i = 0; i < n; qs.push_back(ps[i++])){ 56 while(qs.size() > 1 && crossOp(qs[qs.size()-2], qs.back(), ps[i]) <= 0) 57 qs.pop_back(); 58 } 59 for (int i = n - 2, t = qs.size(); i >= 0; qs.push_back(ps[i--])){ 60 while((int)qs.size() > t && crossOp(qs[(int)qs.size()-2], qs.back(), ps[i]) <= 0) 61 qs.pop_back(); 62 } 63 qs.pop_back(); 64 return qs; 65 } 66 67 vector<point> p, ans; 68 int T; 69 int main() 70 { 71 scanf("%d", &T); 72 int cas = 0; 73 while(T--) 74 { 75 p.clear(); 76 scanf("%d %lf", &n, &d); 77 double x, y; 78 for (int i = 0; i < n; i++){ 79 scanf("%lf %lf", &x ,&y); 80 p.push_back(point(x, y)); 81 } 82 ans = convexHull(p); 83 double C = 0; 84 int size = ans.size(); 85 for (int i = 0; i < size; i++){ 86 C += dis(ans[i], ans[(i+1)%size]); 87 } 88 printf("Case #%d: %.4lf\n", ++cas, C/pi/d); 89 } 90 return 0; 91 }