【无码专区8】三角形二维数点——计数有多少个给定点落在三角形区域内
因為只有std,沒有自我實現,所以是無碼專區
主要是為了訓練思維能力
solution才是dls正解,但是因為只有潦草幾句,所以大部分會有我自己基于正解上面的算法實現過程,可能選擇的算法跟std中dls的實現不太一樣。
std可能也會帶有博主自己的注釋。
problem
平面上有 nnn 個點,(xi,yi)(x_i,y_i)(xi?,yi?)。
qqq 次詢問,每次給定三個點 A(x+d,y),B(x,y),C(x,y+d)A(x+d,y),B(x,y),C(x,y+d)A(x+d,y),B(x,y),C(x,y+d)。
求有多少個點 (xi,yi)(x_i,y_i)(xi?,yi?) 在這個三角形內部或邊界上。
10s,128MB,all≤1e610s,128MB,all\le 1e610s,128MB,all≤1e6。
my idea
給定的三角新很有特點:是等腰直角三角形且直角邊分別與一條坐標軸平行。
并不是任意的一個三角形。那么這個三角形最難處理的自然就是斜邊部分,因為不與坐標軸平行。
如果這是個正方形,就是個很簡單的二維差分數點問題。
我曾試圖用所有的點減去沒有落在指定區域的點,但是發現外圍圖形仍然會被切割成若干個矩形和一個三角形,還是不會計算三角形。?
我曾試圖計算一個點的位置會被多少個三角形覆蓋,對這些三角形貢獻 111。但是查詢的三角形壓根不能找到一種范圍限制能夠說明:橫縱坐標簡單的加減為某個范圍時屬于這個三角形。?
這兩種思路都卡在了三角形重疊部分,更具體而言是沒有找到要貢獻的點坐標的特征性,顯然這應該是正解的思路,這導致我們只能嘗試不是正解的想法看能否優化跑過。
既然題目給的是平行坐標軸的直角三角形,那么這個直角邊一定是切入點。
三角形不好做,但是四邊形好做。
將三角形劃分成一個四邊形,和兩個三角形。
如圖:一個三角形存在很多種切割方式。
四邊形是很容易計算的,三角形就遞歸地切割。所以時間復雜度是跟這個三角形切割掛鉤的。
出于平衡左右時間的思想,我們肯定會選取“正半分”,使得兩個三角形“完全一樣”。
【但是如果只計算整點,“正半分”可能是分數,所以是盡可能地“正半分”,讓兩邊地效率盡可能平衡。】
這種遞歸思想就是——分治。
沒錯,我們可以采取分而治之的做法,遞歸求解計算。
最底層的出口就是直角邊某一條長度為 111 的三角形,統計另一條邊的貢獻即可。
這樣,劃分最多 logloglog 層。
四邊形的計算由于 N×MN\times MN×M 根本開不下,無法預處理然后 O(1)O(1)O(1) 差分求解。
只能采取數據結構計算。
具體而言:
將 nnn 個點按 xxx 第一關鍵字,yyy 第二關鍵字排序。
需要在劃分的一個區間內查詢一個區間的問題。又是它!——主席樹。
以 yyy 建立線段樹,第 iii 個版本維護的是前 iii 個點的主席數信息,權值線段樹。
線段樹上節點維護 y∈[l,r]y\in[l,r]y∈[l,r] 的點有多少個。
i→i+1i\rightarrow i+1i→i+1,單點更新 yiy_iyi?。
查詢 (x1,y1,x2,y2)(x1,y1,x2,y2)(x1,y1,x2,y2)。找到 x1<xlx1<x_lx1<xl? 的最小 lll,以及 xr<x2xr<x2xr<x2 的最大 rrr。二分查找。
然后就詢問 l~rl\sim rl~r 版本中 [y1,y2][y1,y2][y1,y2] 內點的個數。
這樣來看數據結構自帶兩個 logloglog,外層還有一個。時間復雜度是 O(nlog3n)O(nlog^3n)O(nlog3n) 的。
只能堪堪通過 70%70\%70% 的數據點 2×1052\times 10^52×105。
【計算出來大概是 8e88e88e8 的,10s10s10s 跑 1e91e91e9,常數小寫得好看應該是能跑過的。】
考慮優化,優化掉二分查找的 logloglog。
最開始排序的 logloglog 省不了。
對于 iii,直接二分找 l,rl,rl,r 記錄下來 Li,RiL_i,R_iLi?,Ri?。
這樣是將里面嵌套的 logloglog 放在了外面。
O(nlog?3n)→O(nlog?2n+nlog?n)O(n\log^3n)\rightarrow O(n\log^2n+n\log n)O(nlog3n)→O(nlog2n+nlogn)。
就是兩個 logloglog 的了。
算出來 4e84e84e8,相對與 1e91e91e9 而言應該是綽綽有余的,常數大點應該沒關系。
trick:圖上一條線段分裂,但實際上計算的時候并不重疊。
中間界可以劃給三角形部分,也可以是矩形部分。
但具體實現會發現,將中間界劃分給三角形會比較好算,因為是封閉的。
不好說明,給圖直觀感受。
但是也不是說不能寫,應該也是能實現的。
solution
直接查詢,就是三維數點問題。CDQCDQCDQ 分治做 O(nlog?2n)O(n\log^2n)O(nlog2n)。
又來了,一句話一頁闡釋。
為什么是三維數點??怎么數??
擴展:如何用三角形的三個坐標表示三角形中任意一點的坐標。
假設三角形三點分別為 A(x1,y1),B(x2,y2),C(x3,y3)A(x1,y1),B(x2,y2),C(x3,y3)A(x1,y1),B(x2,y2),C(x3,y3),現要表示內部點 O(x,y)O(x,y)O(x,y)。
設射線 AOAOAO 交 BCBCBC 與點 DDD,AO?=mAD?,BD?=nBC?\vec{AO}=m\vec{AD},\vec{BD}=n\vec{BC}AO=mAD,BD=nBC。
則 AO?=mAD?=m(AB?+BD?)=mAB?+mnBC?\vec{AO}=m\vec{AD}=m(\vec{AB}+\vec{BD})=m\vec{AB}+mn\vec{BC}AO=mAD=m(AB+BD)=mAB+mnBC。
即:
(x?x1,y?y1)=m(x2?x1,y2?y1)+mn(x3?x2,y3?y2)(x-x1,y-y1)=m(x2-x1,y2-y1)+mn(x3-x2,y3-y2) (x?x1,y?y1)=m(x2?x1,y2?y1)+mn(x3?x2,y3?y2)
=(m(x2?x1)+mn(x3?x2),m(y2?y1)+mn(y3?y2))=(m(x2-x1)+mn(x3-x2),m(y2-y1)+mn(y3-y2)) =(m(x2?x1)+mn(x3?x2),m(y2?y1)+mn(y3?y2))
?{x?x1=m(x2?x1)+mn(x3?x2)?x=x1(1?m)+x2(m?mn)+x3mny?y1=m(y2?y1)+mn(y3?y2)?y=y1(1?m)+y2(m?mn)+y3mn\Rightarrow\begin{cases}x-x1=m(x2-x1)+mn(x3-x2)\Rightarrow x=x1(1-m)+x2(m-mn)+x3mn\\y-y1=m(y2-y1)+mn(y3-y2)\Rightarrow y=y1(1-m)+y2(m-mn)+y3mn\\\end{cases} ?{x?x1=m(x2?x1)+mn(x3?x2)?x=x1(1?m)+x2(m?mn)+x3mny?y1=m(y2?y1)+mn(y3?y2)?y=y1(1?m)+y2(m?mn)+y3mn?
換元表示 a=1?m,b=m?mna=1-m,b=m-mna=1?m,b=m?mn ,則
{x=a?x1+b?x2+(1?a?b)x3y=a?y1+b?y2+(1?a?b)y3\begin{cases}x=a·x1+b·x2+(1-a-b)x3\\y=a·y1+b·y2+(1-a-b)y3\\\end{cases} {x=a?x1+b?x2+(1?a?b)x3y=a?y1+b?y2+(1?a?b)y3?
dbq,我確實沒看懂這個三維數點怎么做。
觀察性質,三角形區域 (x′,y′)?(x′+d,y′)?(x′,y′+d)(x',y')-(x'+d,y')-(x',y'+d)(x′,y′)?(x′+d,y′)?(x′,y′+d)。
相當于總區域減去 x<x′,y<y′,x+y>x′+y′+dx<x',y<y',x+y>x'+y'+dx<x′,y<y′,x+y>x′+y′+d 三個半平面。
然后補上三個被減了兩次的區域。
即 x<x′∧y<y′,x<x′∧x+y>x′+y′+d′,y<y′∧x+y>x′+y′+dx<x'\wedge y<y',x<x'\wedge x+y>x'+y'+d',y<y'\wedge x+y>x'+y'+dx<x′∧y<y′,x<x′∧x+y>x′+y′+d′,y<y′∧x+y>x′+y′+d。
這三個部分都是二維數點,直接做,時間復雜度 O(nlog?n)O(n\log n)O(nlogn)。
其實看題解的時候我并不懂,但是一看代碼就懂了!!
原來是我不會等腰直角三角形的二維數點!!其實很簡單。
code
#include <bits/stdc++.h> using namespace std; #define FOR(a,b,c) for (int a=b,_c=c;a<=_c;a++) #define FORD(a,b,c) for (int a=b;a>=c;a--) #define REP(i,a) for(int i=0,_a=(a); i<_a; ++i) #define REPD(i,a) for(int i=(a)-1; i>=0; --i) #define pb push_back #define mp make_pair #define fi first #define se second #define sz(a) int(a.size()) #define reset(a,b) memset(a,b,sizeof(a)) #define oo 1000000007using namespace std;typedef long long ll; typedef pair<int, int> pii;const int maxn = 1000007; const int maxv = 1000007;int n, q, BIT[maxn], ans[maxn]; struct node {int x, y, d, id; } a[maxn * 2];bool cmp1(const node &a, const node &b) {return a.y < b.y || (a.y == b.y && a.id > b.id); }bool cmp2(const node &a, const node &b) {int v1 = a.x + a.y + a.d;int v2 = b.x + b.y + b.d;return v1 < v2 || (v1 == v2 && a.id < b.id); }void update(int p) {for (int i = p; i <= maxv; i += i & (-i))BIT[i]++; }int get(int p) {int ans = 0;p = min(p, maxv);for (int i = p; i; i -= i & (-i))ans += BIT[i];return ans; }int main() {freopen("triangular.in", "r", stdin);freopen("triangular.out", "w", stdout);scanf("%d%d", &n, &q);FOR(i, 1, n) {scanf("%d%d", &a[i].x, &a[i].y);a[i].id = 0;}FOR(i, 1, q) {scanf("%d%d%d", &a[i + n].x, &a[i + n].y, &a[i + n].d);a[i + n].id = i;}sort(a + 1, a + n + q + 1, cmp1);FOR(i, 1, n + q)if (a[i].id)ans[a[i].id] -= get(a[i].x + a[i].d) - get(a[i].x - 1);elseupdate(a[i].x);sort(a + 1, a + n + q + 1, cmp2);reset(BIT, 0);FOR(i, 1, n + q)if (a[i].id)ans[a[i].id] += get(a[i].x + a[i].d) - get(a[i].x - 1);elseupdate(a[i].x);FOR(i, 1, q) printf("%d\n", ans[i]);return 0; }總結
這道題大概斷斷續續想了兩天(每天 40min40min40min 左右)吧,當我突然意識到分拆遞歸處理的時候,后面的進展就比較快了。
其實std的做法是很有局限性的,必須充分利用等腰直角,直角邊平行于坐標軸三個條件,但也是挺妙的。
但是我的想法是可以扔掉等腰這個條件的,只需要直角三角形,直角邊平行于坐標軸就可以了,但是多了個 logloglog。
當然這也是我的想法,我也不知道是否正確,歡迎友好討論以及分享不同思路。
總結
以上是生活随笔為你收集整理的【无码专区8】三角形二维数点——计数有多少个给定点落在三角形区域内的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 穿越时空的爱恋主题曲 穿越时空的爱恋主题
- 下一篇: 叁仟柒佰万(mex+线段树+dp+前缀和