# 题目大意给定 n n n 个点,每个点的坐标是 ( x i , y i ) (x_i, y_i) ( x i , y i ) 然后给定 m m m 个询问,每个询问代表,我当前站在原点,给定其从 A A A 和 B B B 请问,我当前由原点指向 ( x A , y A ) (x_A, y_A) ( x A , y A ) 的射线,转到由原点指向 ( x B , y B ) (x_B, y_B) ( x B , y B ) 的射线,一共会扫过多少个点
# 数据范围2 ≤ n ≤ 2 × 1 0 5 2 \le n \le 2 \times 10^5 2 ≤ n ≤ 2 × 1 0 5 1 ≤ q ≤ 2 × 1 0 5 1 \le q \le 2 \times 10^5 1 ≤ q ≤ 2 × 1 0 5 − 1 0 9 ≤ x i , y i ≤ 1 0 9 -10^9 \le x_i, y_i \le 10^9 − 1 0 9 ≤ x i , y i ≤ 1 0 9 # 题解为了解决这道题(不用浮点数的做法)需要掌握:向量与叉积 、极角排序
# 向量与叉积对于向量 a ⃗ = ( x 1 , y 1 ) , b ⃗ = ( x 2 , y 2 ) \vec{a} = (x_1, y_1), \vec{b} = (x_2, y_2) a = ( x 1 , y 1 ) , b = ( x 2 , y 2 ) cross(a, b) = x1 * y2 - y1 * x2
cross(a, b) > 0 :b ⃗ \vec{b} b 在 a ⃗ \vec{a} a 的逆时针方向cross(a, b) < 0 :b ⃗ \vec{b} b 在 a ⃗ \vec{a} a 的顺时针方向cross(a, b) = 0 :b ⃗ \vec{b} b 和 a ⃗ \vec{a} a 共线 注意,这里的顺、逆时针是指从 a ⃗ \vec{a} a 转到 b ⃗ \vec{b} b ,旋转角度 < 180 ° < 180° < 1 8 0 ° 的旋转这是第一个点,但是需要注意的是,这里的方向,并不是射线方向,而是向量所在直线的方向,如果需要同方向的话,还需要点积 dot(a, b) > 0
# 极角排序极角排序模板:
C++ 1 2 3 4 5 6 7 8 9 bool upper (v) { return (v.y > 0 ) || (v.y == 0 && v.x > 0 ) } bool cmp (a, b) { if (upper (a) != upper (b)) return upper (a) > upper (b); return cross (a, b) > 0 ; }
# 综合排序后,我们合并下同向的 然后呢,有个问题就是,可能会跨过 0 ° 0° 0 ° ,跨过的不好计算,所以可以双倍计算前缀和
C++ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 #include <bits/stdc++.h> using namespace std;#define int long long struct Pt { int x, y, id, gid; }; int cross (Pt &a, Pt &b) { return a.x * b.y - a.y * b.x; } int dot (Pt &a, Pt &b) { return a.x * b.x + a.y * b.y; } bool upper (Pt &p) { return p.y > 0 || (p.y == 0 && p.x > 0 ); } signed main () { ios::sync_with_stdio (false ); cin.tie (nullptr ); int n, q; cin >> n >> q; vector<Pt> pts (n) ; for (int i = 0 ; i < n; i++) { cin >> pts[i].x >> pts[i].y; pts[i].id = i; pts[i].gid = -1 ; } sort (pts.begin (), pts.end (), [](Pt &a, Pt &b) { bool ua = upper (a), ub = upper (b); if (ua != ub) return ua > ub; return cross (a, b) > 0 ; }); vector<int > cnt; int gid = -1 ; Pt rep; for (int i = 0 ; i < n; i++) { if (gid == -1 ) { gid = 0 ; cnt.push_back (1 ); rep = pts[i]; pts[i].gid = gid; } else if (cross (rep, pts[i]) == 0 && dot (rep, pts[i]) > 0 ) { cnt[gid]++; pts[i].gid = gid; } else { gid++; cnt.push_back (1 ); rep = pts[i]; pts[i].gid = gid; } } int M = cnt.size (); vector<int > pre (2 * M + 1 , 0 ) ; for (int i = 0 ; i < 2 * M; i++) pre[i + 1 ] = pre[i] + cnt[i % M]; sort (pts.begin (), pts.end (), [](Pt &a, Pt &b){ return a.id < b.id; }); while (q--) { int A, B; cin >> A >> B; A--; B--; int pa = pts[A].gid, pb = pts[B].gid; if (pa == pb) cout << cnt[pa] << "\n" ; else { int l = pb, r = pa; if (r < l) r += M; cout << pre[r + 1 ] - pre[l] << "\n" ; } } }
# 题目大意给定一个 n × m n \times m n × m 的网格,由 # 和 . 组成,其中, # 代表黑色, . 代表白色 现在问,能不能找到一种改色方案,使得改后
对于每一行,存在一个整数 k ( 0 ≤ k < N ) k~(0 \le k < N) k ( 0 ≤ k < N ) ,使得该行左侧的 k k k 个方块是白色,右侧其余的是黑色 对于每一列,存在一个整数 k ( 0 ≤ k < N ) k~(0 \le k < N) k ( 0 ≤ k < N ) ,使得该列上方的 k k k 个方块是白色,下方其余的是黑色 注意,行 和 行 和 列 和 列的 k k k 可以不相同
# 数据范围1 ≤ n ≤ 5000 1 \le n \le 5000 1 ≤ n ≤ 5 0 0 0 # 题解我们先维护好一个 p r e i , j pre_{i, j} p r e i , j 数组,表示第 i i i 行前 j j j 个格子中 . 的个数 那么,对于第 i i i 行,如果以 j j j 点为分界,那么
前 j j j 个格子,需要把 # 改为 . ,也就是要花费 j - pre[i][j] 后面 n − j n - j n − j 个格子,需要把 . 改为 # ,也就是要花费 pre[i][n] - pre[i][j] 总花费为: cost[i][j] = j + pre[i][n] - 2 * pre[i][j]
观察有,如果最终矩阵合法,那么矩阵的行分界点一定是越来越前的 也就是,i − 1 i-1 i − 1 行的分界点 k k k 必须满足 k ≥ j k \ge j k ≥ j 那么我们可以搞出一个 d p i , j dp_{i,j} d p i , j ,表示第 i i i 行以 j j j 点为最后一个白色的最小修改代价,那么可以推导出以下状态转移方程
d p i , j = min j ≤ k ≤ n d p i − 1 , k + c o s t i , j dp_{i,j} = \min_{j \le k \le n} dp_{i - 1,k} + cost_{i,j} d p i , j = j ≤ k ≤ n min d p i − 1 , k + c o s t i , j
C++ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 void solve () { cin >> n; c = vector<vector<char >>(n + 10 , vector <char >(n + 10 )); pre = vector<vector<int >>(n + 10 , vector <int >(n + 10 )); cost = vector<vector<int >>(n + 10 , vector <int >(n + 10 )); dp = vector<vector<int >>(n + 10 , vector <int >(n + 10 )); for (int i = 1 ; i <= n; i++) for (int j = 1 ; j <= n; j++) { cin >> c[i][j]; pre[i][j] = pre[i][j - 1 ] + (c[i][j] == '.' ); } for (int i = 1 ; i <= n; i++) for (int j = 0 ; j <= n; j++) { cost[i][j] = j + pre[i][n] - 2 * pre[i][j]; } for (int i = 1 ; i <= n; i++) { for (int j = 0 ; j <= n; j++) { dp[i][j] = cost[i][j]; int minn = 1e18 ; for (int k = j; k <= n; k++) minn = min (minn, dp[i - 1 ][k]); dp[i][j] += minn; } } int res = 1e18 ; for (int j = 0 ; j <= n; j++) res = min (res, dp[n][j]); cout << res << '\n' ; }
但是这样写代码,复杂度是
O ( n 3 ) O(n^3) O ( n 3 ) 的,数据范围是
5000 5000 5 0 0 0 ,所以还得优化
显然,我们每次只需要求
min j ≤ k ≤ n d p i − 1 , k \min_{j \le k \le n} dp_{i - 1,k} min j ≤ k ≤ n d p i − 1 , k ,那我们不妨维护一个
s u f M i n sufMin s u f M i n 数组,去记录后
j j j 个的
d p dp d p 的最小值
C++ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 void solve () { cin >> n; c = vector<vector<char >>(n + 10 , vector <char >(n + 10 )); pre = vector<vector<int >>(n + 10 , vector <int >(n + 10 )); cost = vector<vector<int >>(n + 10 , vector <int >(n + 10 )); dp = vector<vector<int >>(n + 10 , vector <int >(n + 10 )); sufMin = vector <int >(n + 10 ); for (int i = 1 ; i <= n; i++) for (int j = 1 ; j <= n; j++) cin >> c[i][j], pre[i][j] = pre[i][j - 1 ] + (c[i][j] == '.' ); for (int i = 1 ; i <= n; i++) for (int j = 0 ; j <= n; j++) cost[i][j] = j + pre[i][n] - 2 * pre[i][j]; for (int i = 1 ; i <= n; i++) { for (int j = 0 ; j <= n; j++) { dp[i][j] = cost[i][j] + sufMin[j]; } sufMin[n] = dp[i][n]; for (int j = n - 1 ; j >= 0 ; j--) sufMin[j] = min (dp[i][j], sufMin[j + 1 ]); } int res = 1e18 ; for (int j = 0 ; j <= n; j++) res = min (res, dp[n][j]); cout << res << '\n' ; }