タイトル
トピックリンク:https://loj.ac/problem/2880
JOI村上記の荒れ地の林立あります\(N \)かかしが。任意の2つが同じ横軸、任意の2つが同じでない縦かかしないかかし。村人たちはかかしの周りは毎年いくつかのフェスティバルを開催しました。
ある時、JOI村長は、かかしの啓示、荒れ地での埋め立て計画を聞きました。そして、同じ啓示は、土地は、以下の基準を満たす必要があります:
- フィールドの形状は、矩形座標軸の辺に平行です。
- コーナーはかかしを持っています。
- (国境を含まない)内部フィールドなしかかし。
各かかしの座標を与え、あなたはフィールドの状態が得られている満たすどのように多くの参照してください。
思考
問題を解決するため、インターネットを使用している(CDQ + \)\単調なスタック優れたアルゴリズムが、(\色{グレー} {\ \ texttt { \チキンstoorzの料理は}})あまりにも料理で、これらの背の高い、まだアルゴリズムを理解していません。そう書いた\(CDQ +ビット+ \)超超超大型ごみアルゴリズム定数のツリーラインの一種\(qwqを\) 。そして、Tフライにbzoj、LOJは、33321ミリ秒最後で走りました
まず、我々は、このトピックは、3次元部分順序がそう使用することを検討して、を求めて多くのように実際にあることがわかった\(CDQを\)解決します。
私たちは今、間隔があることを仮定\([L、R] \) 、すべてのポイントのために我々が貢献の範囲を見つけることができます\([L_iを、R_iと] \) 、例えば、ポイントであれば\(I \)で左半分、列間隔\([L_iを、R_iを] \) 、行間隔\([X_I、中間]は\ ) 矩形内の任意の点でありません。
栗の場合は、図を以下に示します。
寄与範囲赤い点は紫+赤い矩形間隔であり、間隔寄与青いドット列は紫+青矩形断面の列です。黄色、緑色のドット寄与間隔は、長方形の色の列の間隔です。
換言すれば、\(MID \)左に寄与ポイントの範囲の下限は、点の縦座標で、右の間隔点の寄与の上限は、点の垂直座標です。
なぜそれが貢献し、この範囲の外に対処しますか?我々は、もしそれを見つけたので\(中旬\)左の点\(\)と\(中旬\)の右のポイント(\ b)は\満たす\(中y_a \ [L_B、 R_B] \) と\(y_b \ [L_A、R_A] \)で、2つの点が四角形を形成することができ、矩形内の他のポイントが存在しないこと。地図上の、例えば、左右のイエロードットの上に青色のドットが範囲に互いの寄与でその縦軸を満たすためには、左下隅に青いドットので、イエロードット、矩形の右上隅は、正当な矩形です。
だから我々は今、解決するには、2つの問題があります。
- インターバルの各点の寄与を入手するには?
- ポイントはそれぞれ独自の垂直軸を得含むセクションを貢献するには?
ステップバイステップで解決します。
1.どのように各点の範囲の寄与を取得するには?
よると、\(CDQ \) 、我々定期的なルーチン\(MID \)が左とポイントの右側を縦でソートされています。
例えば、左側の一部を取得します。点\(x \)別のポイントに影響を与えます\(Y \)寄与範囲、場合に限り、\(x \)厳密に位置(Y \)\右下隅です。この場合、\(Y \)少なくともの寄与間隔の下限\(X \)縦。
そう左点について、我々は、セグメントツリー、区間木セグメント内蔵\([L、R]が\ ) 横軸を表し(L \ SIM R \)\点、最大座標値。我々はポイントを列挙している場合を押して縦軸は、小から大まで列挙のシーケンス順序を排水\(I \) 、この場合は、セグメントツリーに挿入されています\(1 \ simのI-1 \) ポイント、[オンラインセグメントツリークエリ\([X_I、中間] \ ) の最大値を、最大寄与限界を見つけることは容易では、この点の範囲です。その後、\(Iは、\)の計算を継続するためにダウンし、挿入します。
だからでは、その(O(N Nログ\) \)\ インターバル全ての時間複雑内得\([L、R] \ ) 区間内寄与ポイント。上記の例では、なお\(中旬\)左側治療ポイントは、\(中旬\)いくつかの変更の必要性の右側のポイントは、自分で考える(コードにしたくない)\ \(qwqを)。
ポイントを含むセクションを貢献する方法2.それぞれ独自の縦軸を得ましたか?
最初の反応は、Moのチームが、チームの複雑さは、ミズーリ(N \ SQRT {N} \ \) レベル、この質問\(N- \のLeq 2 \ ^ 10 5回\) 、プラス\(CDQ \)\(\ログのn \)とスーパー定数、明らかではありません。
点列挙いま、縦軸に応じて大列挙にまだ小さい左、上に列挙点を考える\(kは\) 、我々はポインタの右半分は、点を含む維持することができる\(K \)ポイント。具体的には、右半分のポイントの範囲の一部としてだけでなく、新たな配列、範囲の限界寄与に応じて、元の配列に貢献コピーする\((L)\)部分の寄与に応じて配列をコピーした後、小から大への上限\((R&LT)\)小から大にし、2つのポインタを維持する\(I、Jを\)プレス用の2つの走査アレイである、\(Lは\)アレイ、もしソート\([I]を。 L \ 1当量[K]は.Y \) 、我々はツリーアレイ置く\([I] .R \ ) 用プレス;位置プラスワン\(R&LT \)ソートされた配列、もし\(Bを[J] .R <[K]・Y \) 、我々はツリーアレイ置く\(B [J] .Y \ ) から1を引いたものを、そうダウン、ツリーポイントアレイ部1は貢献であります下限\(K \)縦軸の下、上限未満でない間(K \)\にある点の縦座標、(1 \)\間隔点の寄与は含ま\(kは\) 。
その後、次の長いほど\(K \)寄与範囲はこれらの点はでフェンウィックツリーの非常に単純なクエリー間隔とすることができる含ま\([K]・L 、[K] .R] \) のそして、することができます。
このように、我々は、(O(N Nログ\) \)\ 時間複雑さの間隔内で得られる\([L、R] \ ) ポイントの左半分の寄与の右半分にポイント。したがって、間隔\([L、R] \ )、 全ての処理が終了すると、次の再帰的溶液。
時間複雑\(O(N \ログ^ N-2)\) 。定数素晴らしいです。
注開くには(\長い\ RM長い\ \ )
コードワードはそんなに私は疲れqwqwqよ
コード
\(185 \)ライン、およそ\(28180b \) 。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll N=200010,Inf=1e9;
ll n,tot,ans,maxn,c[N*2];
struct node
{
ll x,y,l,r;
}a[N],b[N];
inline bool cmp1(node x,node y)
{
return x.x<y.x;
}
inline bool cmp2(node x,node y)
{
return x.y<y.y;
}
inline bool cmp3(node x,node y)
{
return x.l<y.l;
}
inline bool cmp4(node x,node y)
{
return x.r<y.r;
}
struct BIT
{
ll c[N];
inline void add(ll x,ll val)
{
for (;x<=maxn;x+=x&-x)
c[x]+=val;
}
inline ll ask(ll x)
{
ll ans=0;
for (;x;x-=x&-x)
ans+=c[x];
return ans;
}
inline void clear(ll x)
{
for (;x<=maxn && c[x];x+=x&-x)
c[x]=0;
}
}bit;
struct Treenode
{
ll l,r,minn,maxn;
};
struct Tree
{
Treenode tree[N*8];
inline void build(ll x,ll l,ll r)
{
tree[x].l=l; tree[x].r=r;
if (l==r)
{
tree[x].maxn=1; tree[x].minn=maxn;
return;
}
ll mid=(l+r)>>1;
build(x*2,l,mid); build(x*2+1,mid+1,r);
pushup(x);
}
inline void pushup(ll x)
{
tree[x].maxn=max(tree[x*2].maxn,tree[x*2+1].maxn);
tree[x].minn=min(tree[x*2].minn,tree[x*2+1].minn);
}
inline void update(ll x,ll k,ll val)
{
if (tree[x].l==k && tree[x].r==k)
{
tree[x].maxn=max(tree[x].maxn,val);
tree[x].minn=min(tree[x].minn,val);
return;
}
ll mid=(tree[x].l+tree[x].r)>>1;
if (k<=mid) update(x*2,k,val);
else update(x*2+1,k,val);
pushup(x);
}
inline ll ask(ll x,ll l,ll r,bool type)
{
if (tree[x].l==l && tree[x].r==r)
return type ? tree[x].maxn : tree[x].minn;
ll mid=(tree[x].l+tree[x].r)>>1;
if (r<=mid) return ask(x*2,l,r,type);
else if (l>mid) return ask(x*2+1,l,r,type);
else
{
if (type) return max(ask(x*2,l,mid,type),ask(x*2+1,mid+1,r,type));
else return min(ask(x*2,l,mid,type),ask(x*2+1,mid+1,r,type));
}
}
inline void clear(ll x,ll k)
{
tree[x].maxn=1; tree[x].minn=n;
if (tree[x].l==tree[x].r) return;
ll mid=(tree[x].l+tree[x].r)>>1;
if (k<=mid) clear(x*2,k);
else clear(x*2+1,k);
}
}Tree;
inline void cdq(ll l,ll r)
{
if (l==r) return;
ll mid=(l+r)>>1;
cdq(l,mid); sort(a+l,a+mid+1,cmp2);
cdq(mid+1,r); sort(a+mid+1,a+r+1,cmp2);
for (ll i=mid;i>=l;i--)
{
a[i].r=Tree.ask(1,a[i].x,maxn,0); a[i].l=a[i].y+1;
Tree.update(1,a[i].x,a[i].y);
}
for (ll i=mid;i>=l;i--) Tree.clear(1,a[i].x);
for (ll i=mid+1;i<=r;i++)
{
a[i].l=Tree.ask(1,1,a[i].x,1); a[i].r=a[i].y-1;
Tree.update(1,a[i].x,a[i].y);
b[i]=a[i];
}
for (ll i=mid+1;i<=r;i++) Tree.clear(1,a[i].x);
sort(a+mid+1,a+r+1,cmp3); sort(b+mid+1,b+r+1,cmp4);
for (ll i=mid+1,j=mid+1,k=l;k<=mid;k++)
{
for (;a[j].l<=a[k].y && j<=r;j++)
bit.add(a[j].y,1);
for (;b[i].r<a[k].y && i<=r;i++)
bit.add(b[i].y,-1);
ans+=bit.ask(a[k].r)-bit.ask(a[k].l-1);
}
for (ll i=l;i<=r;i++)
bit.clear(a[i].y);
}
int main()
{
scanf("%lld",&n);
for (ll i=1;i<=n;i++)
scanf("%lld%lld",&a[i].x,&a[i].y);
for (ll i=1;i<=n;i++) c[i]=a[i].x;
sort(c+1,c+1+n);
tot=unique(c+1,c+1+n)-c-1;
for (ll i=1;i<=n;i++)
{
a[i].x=lower_bound(c+1,c+1+tot,a[i].x)-c;
maxn=max(maxn,a[i].x);
}
for (ll i=1;i<=n;i++) c[i]=a[i].y;
sort(c+1,c+1+n);
tot=unique(c+1,c+1+n)-c-1;
for (ll i=1;i<=n;i++)
{
a[i].y=lower_bound(c+1,c+1+tot,a[i].y)-c;
maxn=max(maxn,a[i].y);
}
Tree.build(1,1,maxn);
sort(a+1,a+1+n,cmp1);
cdq(1,n);
printf("%lld",ans);
return 0;
}