一个人做了12年北大出的题,自己还是太弱了,图论的知识忘光光,最小生成树裸题写不来,Dijkstra TLE不知道用SPFA。
简单几何(点到线段的距离) + 三分 B Stealing a Cake
题意:圆外一个点先到圆再到矩形的最短距离。
分析:由于圆在[0, PI]和[PI, PI*2]上是单峰函数,用三分求极值,应该在[0,PI*2]上也算单峰函数吧。
/************************************************ * Author :Running_Time * Created Time :2015/11/7 星期六 17:24:32 * File Name :B_2.cpp ************************************************/ #include <cstdio> #include <algorithm> #include <iostream> #include <sstream> #include <cstring> #include <cmath> #include <string> #include <vector> #include <queue> #include <deque> #include <stack> #include <list> #include <map> #include <set> #include <bitset> #include <cstdlib> #include <ctime> using namespace std; #define lson l, mid, rt << 1 #define rson mid + 1, r, rt << 1 | 1 typedef long long ll; const int N = 1e5 + 10; const int INF = 0x3f3f3f3f; const int MOD = 1e9 + 7; const double EPS = 1e-10; const double PI = acos (-1.0); int dcmp(double x) { //三态函数,减少精度问题 if (fabs (x) < EPS) return 0; else return x < 0 ? -1 : 1; } struct Point { //点的定义 double x, y; Point () {} Point (double x, double y) : x (x), y (y) {} Point operator + (const Point &r) const { //向量加法 return Point (x + r.x, y + r.y); } Point operator - (const Point &r) const { //向量减法 return Point (x - r.x, y - r.y); } Point operator * (double p) const { //向量乘以标量 return Point (x * p, y * p); } Point operator / (double p) const { //向量除以标量 return Point (x / p, y / p); } bool operator < (const Point &r) const { //点的坐标排序 return x < r.x || (x == r.x && y < r.y); } bool operator == (const Point &r) const { //判断同一个点 return dcmp (x - r.x) == 0 && dcmp (y - r.y) == 0; } }; typedef Point Vector; //向量的定义 Point read_point(void) { //点的读入 double x, y; scanf ("%lf%lf", &x, &y); return Point (x, y); } double dot(Vector A, Vector B) { //向量点积 return A.x * B.x + A.y * B.y; } double cross(Vector A, Vector B) { //向量叉积 return A.x * B.y - A.y * B.x; } Vector rotate(Vector A, double rad) { return Vector (A.x * cos (rad) - A.y * sin (rad), A.x * sin (rad) + A.y * cos (rad)); } double length(Vector A) { return sqrt (dot (A, A)); } double angle(Vector A, Vector B) { //向量转角,逆时针,点积 return acos (dot (A, B) / length (A) / length (B)); } double point_to_seg(Point p, Point a, Point b) { if (a == b) return length (p - a); Vector V1 = b - a, V2 = p - a, V3 = p - b; if (dcmp (dot (V1, V2)) < 0) return length (V2); else if (dcmp (dot (V1, V3)) > 0) return length (V3); else return fabs (cross (V1, V2)) / length (V1); } struct Circle { Point c; double r; Circle () {} Circle (Point c, double r) : c (c), r (r) {} Point point(double a) { return Point (c.x + cos (a) * r, c.y + sin (a) * r); } }; Point s, a, b, c, d; Circle C; double cal_dis(double rad) { Point p = C.point (rad); double dis1 = length (s - p); double dis2 = 1e9; dis2 = min (dis2, point_to_seg (p, a, b)); dis2 = min (dis2, point_to_seg (p, b, c)); dis2 = min (dis2, point_to_seg (p, c, d)); dis2 = min (dis2, point_to_seg (p, d, a)); return dis1 + dis2; } int main(void) { double x, y, r, x2, y2; while (scanf ("%lf%lf", &x, &y) == 2) { if (dcmp (x) == 0 && dcmp (y) == 0) break; s = Point (x, y); scanf ("%lf%lf%lf", &x, &y, &r); C = Circle (Circle (Point (x, y), r)); scanf ("%lf%lf%lf%lf", &x, &y, &x2, &y2); a = Point (min (x, x2), max (y, y2)); b = Point (min (x, x2), min (y, y2)); c = Point (max (x, x2), min (y, y2)); d = Point (max (x, x2), max (y, y2)); Vector V[2]; int cnt = point_cir_tan (s, C, V); double l = angle (Vector (1, 0), V[0]), r = angle (Vector (1, 0), V[1]); if (l > r) swap (l, r); while (r - l > EPS) { double mid = (l + r) / 2; double lmid = (l + mid) / 2; double rmid = (r + mid) / 2; double res1 = cal_dis (lmid), res2 = cal_dis (rmid); if (res1 < res2) { r = rmid; } else l = lmid; } printf ("%.2f ", cal_dis (l)); } return 0; }
预处理+递推DP C Substrings
题意:给n个数字,q次询问,每次回答所有长度为w的子串不同数字个数的和
分析:先想状态,dp[w]表示长度为w的不同数字总和,有个递推关系,dp[i] = dp[i-1] - last[i-1] + sum[i]。last[i]表示后缀长度为i的不同个数,从i-1到i时能发现少了最后一个长度i-1的子串,当然还要加上新的权值,sum[i]表示两个相同数字距离大于等于i的所有个数,那么先求c[i] 等于i的所有个数,后缀累加就行了。这题充分使用了预处理的技巧求解问题,复杂度 O (n)
/************************************************ * Author :Running_Time * Created Time :2015/11/9 星期一 10:23:58 * File Name :C.cpp ************************************************/ #include <bits/stdc++.h> using namespace std; #define lson l, mid, rt << 1 #define rson mid + 1, r, rt << 1 | 1 typedef long long ll; const int N = 1e6 + 10; const int INF = 0x3f3f3f3f; const int MOD = 1e9 + 7; const double EPS = 1e-10; const double PI = acos (-1.0); ll dp[N]; int a[N], pre[N], sum[N], last[N], c[N]; int main(void) { int n, q; while (scanf ("%d", &n) == 1) { if (!n) break; for (int i=1; i<=n; ++i) scanf ("%d", &a[i]); memset (c, 0, sizeof (c)); memset (pre, 0, sizeof (pre)); for (int i=1; i<=n; ++i) { c[i-pre[a[i]]]++; pre[a[i]] = i; } sum[n] = c[n]; for (int i=n-1; i>=1; --i) { sum[i] = sum[i+1] + c[i]; } memset (c, 0, sizeof (c)); last[1] = 1; c[a[n]]++; for (int i=2; i<=n; ++i) { //length if (c[a[n-i+1]] == 0) { last[i] = last[i-1] + 1; c[a[n-i+1]] = 1; } else last[i] = last[i-1]; } dp[1] = n; for (int i=2; i<=n; ++i) { dp[i] = dp[i-1] - last[i-1] + sum[i]; } scanf ("%d", &q); while (q--) { int w; scanf ("%d", &w); printf ("%I64d ", dp[w]); } } //cout << "Time elapsed: " << 1.0 * clock() / CLOCKS_PER_SEC << " s. "; return 0; }
SPFA/BFS H Friend Chains
题意:就是求任意两点的最短路的最大值
分析:Floyd O(n^3)绝对超时,我想了Dijkstra,O (V * (VlogE + V),后来用了SPFA倒是勉勉强强过了,对Dijkstra无爱了。杭电discuss里有人写了最快的解法,类似于求树的直径,两边BFS。问题是第一次找最远的点可能有多个,要选择度数最小的,否则度数多了,从那个点出发到某些点并不是最远的,也就不是直径。
SPFA
#include <bits/stdc++.h> using namespace std; const int N = 1e3 + 10; const int E = 2e4 + 10; const int INF = 0x3f3f3f3f; struct Edge { int v, w, nex; Edge () {} Edge (int v, double w, int nex) : v (v), w (w), nex (nex) {} }edge[E]; int head[N]; bool vis[N]; int d[N]; int n, m, e; void init(void) { memset (head, -1, sizeof (head)); e = 0; } void add_edge(int u, int v, int w) { edge[e] = Edge (v, w, head[u]); head[u] = e++; } void SPFA(int s) { for (int i=1; i<=n; ++i) { vis[i] = false; d[i] = INF; } vis[s] = true; d[s] = 0; queue<int> Q; Q.push (s); while (!Q.empty ()) { int u = Q.front (); Q.pop (); vis[u] = false; for (int i=head[u]; ~i; i=edge[i].nex) { int v = edge[i].v, w = edge[i].w; if (d[v] > d[u] + w) { d[v] = d[u] + w; if (!vis[v]) { vis[v] = true; Q.push (v); } } } } } map<string, int> id; char str1[11], str2[11]; int main(void) { while (scanf ("%d", &n) == 1) { if (!n) break; id.clear (); for (int i=1; i<=n; ++i) { scanf ("%s", &str1); id[str1] = i; } init (); scanf ("%d", &m); for (int i=1; i<=m; ++i) { scanf ("%s%s", &str1, &str2); if (id[str1] == id[str2]) continue; add_edge (id[str1], id[str2], 1); add_edge (id[str2], id[str1], 1); } int ans = 0; for (int i=1; i<=n; ++i) { SPFA (i); for (int j=1; j<=n; ++j) { if (d[j] == INF) { ans = -1; break; } if (ans < d[j]) ans = d[j]; } if (ans == -1) break; } printf ("%d ", ans); } return 0; }
BFS
/************************************************ * Author :Running_Time * Created Time :2015/11/7 星期六 16:16:25 * File Name :H_2.cpp ************************************************/ #include <bits/stdc++.h> using namespace std; #define lson l, mid, rt << 1 #define rson mid + 1, r, rt << 1 | 1 typedef long long ll; const int N = 1e3 + 10; const int E = 2e4 + 10; const int INF = 0x3f3f3f3f; const int MOD = 1e9 + 7; const double EPS = 1e-10; const double PI = acos (-1.0); struct Edge { int v, w, nex; Edge () {} Edge (int v, int w, int nex) : v (v), w (w), nex (nex) {} }edge[E]; int head[N], d[N], deg[N]; bool vis[N]; int n, m, e; void init(void) { memset (head, -1, sizeof (head)); memset (deg, 0, sizeof (deg)); e = 0; } void add_edge(int u, int v, int w) { edge[e] = Edge (v, w, head[u]); head[u] = e++; } int BFS(int s) { for (int i=1; i<=n; ++i) { d[i] = (i == s) ? 0 : INF; vis[i] = (i == s) ? true : false; } queue<int> Q; Q.push (s); int idx = 0, mx = 0; while (!Q.empty ()) { int u = Q.front (); Q.pop (); for (int i=head[u]; ~i; i=edge[i].nex) { int v = edge[i].v, w = edge[i].w; if (vis[v]) continue; if (d[v] > d[u] + w) { d[v] = d[u] + w; vis[v] = true; Q.push (v); } if (d[v] > mx || (d[v] == mx && deg[v] < deg[idx])) { mx = d[v]; idx = v; } } } return idx; } map<string, int> id; char str1[11], str2[11]; int main(void) { while (scanf ("%d", &n) == 1) { if (!n) break; id.clear (); for (int i=1; i<=n; ++i) { scanf ("%s", &str1); id[str1] = i; } init (); scanf ("%d", &m); for (int i=1; i<=m; ++i) { scanf ("%s%s", &str1, &str2); if (id[str1] == id[str2]) continue; add_edge (id[str1], id[str2], 1); add_edge (id[str2], id[str1], 1); deg[id[str1]]++; deg[id[str2]]++; } int ans = 0; int idx = BFS (1); ans = d[BFS (idx)]; for (int i=1; i<=n; ++i) { if (d[i] == INF) { ans = -1; break; } } printf ("%d ", ans); } return 0; }
比赛前走了一盘象棋,竟然就碰到了关于象棋的题目。
状态压缩+枚举 J Scaring the Birds
题意:有几个点可以放稻草人,问最少放几个稻草人才能保护所以稻草
分析:没什么陷阱吧,主要看样例的R的范围有些纠结,放稻草人的点没有稻草的。只要状压枚举要放的数量和位置就可以了。
#include <bits/stdc++.h> using namespace std; const int N = 55; const int INF = 0x3f3f3f3f; struct Point { int x, y, r, step; Point () {} Point (int x, int y, int step) : x (x), y (y), step (step) {} }p[11]; int n, m; int a[N][N]; bool vis[N][N]; int dx[] = {-1, 1, 0, 0}; int dy[] = {0, 0, -1, 1}; void map_init(void) { for (int i=1; i<=n; ++i) { for (int j=1; j<=n; ++j) a[i][j] = 0; } for (int i=0; i<m; ++i) { a[p[i].x][p[i].y] = 1; } } bool judge(void) { for (int i=1; i<=n; ++i) { for (int j=1; j<=n; ++j) { if (!a[i][j]) return false; } } return true; } bool check(int x, int y) { if (x < 1 || x > n || y < 1 || y > n || vis[x][y]) return false; else return true; } void BFS(int u) { int R = p[u].r; vis[p[u].x][p[u].y] = true; queue<Point> Q; Q.push (Point (p[u].x, p[u].y, 0)); while (!Q.empty ()) { Point r = Q.front (); Q.pop (); if (r.step > R) continue; a[r.x][r.y] = 1; for (int i=0; i<4; ++i) { int tx = r.x + dx[i], ty = r.y + dy[i]; if (!check (tx, ty)) continue; vis[tx][ty] = true; Q.push (Point (tx, ty, r.step + 1)); } } } int main(void) { while (scanf ("%d", &n) == 1) { if (!n) break; scanf ("%d", &m); for (int i=0; i<m; ++i) { scanf ("%d%d", &p[i].x, &p[i].y); } for (int i=0; i<m; ++i) { scanf ("%d", &p[i].r); } int S = 1 << m; int ans = INF; for (int i=0; i<S; ++i) { int num = __builtin_popcount (i); map_init (); for (int j=0; j<m; ++j) { if (i & (1 << j)) { memset (vis, false, sizeof (vis)); BFS (j); } } if (!judge ()) continue; ans = min (ans, num); } if (ans == INF) puts ("-1"); else printf ("%d ", ans); } return 0; }
最小生成树 K Outlets
题意:求最小生成树,限制是p和q一定要有边连
分析:作为第二水的题目我竟然不会写,kruskal的方法前先将p和q连上边就行了,或者Prim在p到q的距离变为0(优先出队列),然后再加回去。
Kruskal
#include <bits/stdc++.h> using namespace std; const int N = 55; const int E = N * N; const double INF = 1e9; struct Edge { int u, v; double w; Edge () {} Edge (int u, int v, double w) : u (u), v (v), w (w) {} bool operator < (const Edge &r) const { return w < r.w; } }edge[E]; struct UF { int rt[N], rk[N]; void init(void) { memset (rt, -1, sizeof (rt)); memset (rk, 0, sizeof (rk)); } int Find(int x) { return rt[x] == -1 ? x : rt[x] = Find (rt[x]); } void Union(int x, int y) { x = Find (x); y = Find (y); if (x == y) return ; if (rk[x] > rk[y]) { rt[y] = x; rk[x] += rk[y]; } else { rt[x] = y; rk[y] += rk[x]; } } }uf; int x[N], y[N]; int tot; double sqr(int x, int y) { return (double) (x * x + y * y); } double cal_dis(int i, int j) { return sqrt (sqr (x[i] - x[j], y[i] - y[j])); } void add_edge(int u, int v, double w) { edge[tot++] = Edge (u, v, w); } int p, q; int main(void) { int n; while (scanf ("%d", &n) == 1) { if (!n) break; scanf ("%d%d", &p, &q); if (p > q) swap (p, q); for (int i=1; i<=n; ++i) { scanf ("%d%d", &x[i], &y[i]); } uf.init (); tot = 0; double ans = cal_dis (p, q); uf.rt[p] = q; for (int i=1; i<=n; ++i) { for (int j=i+1; j<=n; ++j) { add_edge (i, j, cal_dis (i, j)); } } sort (edge, edge+tot); for (int i=0; i<tot; ++i) { int u = edge[i].u, v = edge[i].v; double w = edge[i].w; u = uf.Find (u); v = uf.Find (v); if (u == v) continue; ans += w; uf.Union (u, v); } printf ("%.2f ", ans); } return 0; }
Prim
#include <bits/stdc++.h> using namespace std; const int N = 55; const int E = N * N; const double INF = 1e9; struct Edge { int v, nex; double w; Edge () {} Edge (int v, double w, int nex) : v (v), w (w), nex (nex) {} bool operator < (const Edge &r) const { return w > r.w; } }edge[E]; int head[N]; bool vis[N]; double d[N]; int x[N], y[N]; int p, q, e; void init(void) { memset (head, -1, sizeof (head)); e = 0; } void add_edge(int u, int v, double w) { edge[e] = Edge (v, w, head[u]); head[u] = e++; } double sqr(int x, int y) { return (double) (x * x + y * y); } double cal_dis(int i, int j) { return sqrt (sqr (x[i] - x[j], y[i] - y[j])); } double Prim(int s) { memset (vis, false, sizeof (vis)); for (int i=0; i<55; ++i) d[i] = INF; priority_queue<Edge> Q; double ret = 0; for (int i=head[s]; ~i; i=edge[i].nex) { int v = edge[i].v; double w = edge[i].w; if (d[v] > w) { d[v] = w; if (v == q) { ret += d[v]; d[v] = 0; } Q.push (Edge (v, d[v], 0)); } } vis[s] = true; d[s] = 0; while (!Q.empty ()) { int u = Q.top ().v; Q.pop (); if (vis[u]) continue; vis[u] = true; ret += d[u]; for (int i=head[u]; ~i; i=edge[i].nex) { int v = edge[i].v; double w = edge[i].w; if (v == q) continue; if (!vis[v] && d[v] > w) { d[v] = w; Q.push (Edge (v, d[v], 0)); } } } return ret; } int main(void) { int n; while (scanf ("%d", &n) == 1) { if (!n) break; scanf ("%d%d", &p, &q); if (p > q) swap (p, q); for (int i=1; i<=n; ++i) { scanf ("%d%d", &x[i], &y[i]); } init (); for (int i=1; i<=n; ++i) { for (int j=i+1; j<=n; ++j) { add_edge (i, j, cal_dis (i, j)); add_edge (j, i, cal_dis (i, j)); } } printf ("%.2f ", Prim (p)); } return 0; }