linkc++
能够发现题目在模拟 Prim 算法求最大生成树的过程,树边故答案与起点 \(C\) 无关。
先把全部区间离散化,注意对于一个区间 \([l,r]\),要把 \(l,l+1,r\) 三个位置提出来离散化,做为全部新区间的左端点,这样总共只有 \(O(n)\) 个新区间。提 \(l+1\) 的缘由待会再说。
则从起点 \(C\) 出发,寻找全部覆盖 \(C\) 的区间,将这个区间连向的另外一个区间扔进大根堆,键值为边权,并删除这个区间。每次取出大根堆堆顶的区间,寻找该区间最左边的一个未被邀请的点,再从该点出发寻找全部覆盖该点的区间,以此类推。
为何要把每一个区间的左端点单独拆成一个新点(即离散化时要把 \(l+1\) 提出来)呢?考虑这么一种状况:对于一对相连区间,设这对区间中没有子区间,当从外部第一次更新到 这对区间中的某一个区间 时,该区间内的全部点的答案可能不一样,即外部先邀请到这对区间的任意一个左端点,而后该左端点借助其和 这对区间中的另外一个区间 的连边,通过另外一个区间,而后再走若干步,最后回到这个区间时可能会有更大的答案。若是不把左端点拆出来的话,第一次更新到 这对区间中的某一个区间 时,算出来的就是没考虑这对区间的连边时的答案(咱们已经假设这对区间中没有子区间,故原来的 \([l,r]\) 区间在离散化后必定是一个点,答案被认为是统一的),因此答案可能会变小。git
而后考虑模拟细节:
寻找全部覆盖第 \(x\) 个位置的区间。先把全部区间的编号和右端点合为一个二元组 扔到其左端点上,而后把每一个点上全部二元组按右端点从大到小排序,查询时枚举第 \(1\) 到 \(x\) 个位置,对每一个位置从前日后一直取出并删除二元组,取出全部右端点 \(\ge x\) 的二元组,每一个二元组就对应一个覆盖第 \(x\) 个位置的区间。
查找某个区间 \([l,r]\) 最左边的一个未被邀请的点。用并查集,初始时每一个点的父亲都是本身,一个点被邀请则将父亲设为右侧点。这样直接 \(find(l)\) 就找到了。算法
复杂度 \(O(n\log n)\)(删除每一个区间的最坏时间是 \(O(\log n)\))ui
#include<bits/stdc++.h> #define ll long long const int N = (int)1e5 + 5; #define pii pair<int,int> #define mp make_pair #define fi first #define se second #define pb push_back using namespace std; inline int read(){ int x=0; bool f=1; char c=getchar(); for(;!isdigit(c); c=getchar()) if(c=='-') f=0; for(; isdigit(c); c=getchar()) x=(x<<3)+(x<<1)+(c^'0'); if(f) return x; return 0-x; } int A,B,n,cnt,num[N*6],len[N*6],sum; ll ans; struct info{int l1,r1,l2,r2,v;} a[N]; struct seg{ int l,r,v; inline bool operator < (const seg& a)const{ return v<a.v; } }; priority_queue<seg> Q; int f[N*6]; int find(int x) {return x==f[x] ? x : f[x]=find(f[x]);} bool vis[N]; void add_group(int x){ if(vis[x]) return; vis[x]=1; Q.push((seg){a[x].l1,a[x].r1,a[x].v}), Q.push((seg){a[x].l2,a[x].r2,a[x].v}); } namespace SegTree{ #define ls o<<1 #define rs o<<1|1 vector<pii> vec[N*24]; int s[N*24],p[N*24]; void ins(int o, int l, int r, int x, int v1, int v2){ if(l==r) {vec[o].pb(mp(v1,v2)); return;} int mid=l+r>>1; if(x<=mid) ins(ls,l,mid,x,v1,v2); else ins(rs,mid+1,r,x,v1,v2); } inline void pushup(int o) {s[o] = max(s[ls],s[rs]);} void build(int o, int l, int r){ if(l==r){ if(vec[o].empty()) s[o] = -1; else sort(vec[o].begin(),vec[o].end(),greater<pii>()), s[o] = vec[o][0].fi; return; } int mid=l+r>>1; build(ls,l,mid), build(rs,mid+1,r); pushup(o); } void mdf(int o, int l, int r, int x){ if(s[o]<x) return; if(l==r){ while(p[o]<vec[o].size() && vec[o][p[o]].fi>=x) add_group(vec[o][p[o]++].se); s[o] = (p[o]<vec[o].size() ? vec[o][p[o]].fi : -1); return; } int mid=l+r>>1; mdf(ls,l,mid,x); if(x>mid) mdf(rs,mid+1,r,x); pushup(o); } #undef ls #undef rs } using namespace SegTree; void invite(int x, int v){ f[x] = x+1; mdf(1,1,cnt,x); sum += len[x]; ans += (ll)len[x]*v; } inline int lsh(int x) {return upper_bound(num+1,num+cnt+1,x)-num-1;} int main(){ A=read(), B=read(), read(), n=read(); for(int i=1; i<=n; ++i){ a[i].l1=read(), a[i].r1=read(), a[i].l2=read()+A, a[i].r2=read()+A, a[i].v=read(); num[++cnt]=a[i].l1, num[++cnt]=a[i].l1+1, num[++cnt]=a[i].r1+1, num[++cnt]=a[i].l2, num[++cnt]=a[i].l2+1, num[++cnt]=a[i].r2+1; } num[++cnt]=1, num[++cnt]=2; sort(num+1,num+cnt+1), cnt=unique(num+1,num+cnt+1)-num-1; for(int i=1; i<=n; ++i){ a[i].l1 = lsh(a[i].l1), a[i].r1 = lsh(a[i].r1), a[i].l2 = lsh(a[i].l2), a[i].r2 = lsh(a[i].r2); } --cnt; for(int i=1; i<=cnt; ++i) len[i] = num[i+1]-num[i]; for(int i=1; i<=cnt+1; ++i) f[i] = i; for(int i=1; i<=n; ++i) ins(1,1,cnt,a[i].l1,a[i].r1,i), ins(1,1,cnt,a[i].l2,a[i].r2,i); build(1,1,cnt); invite(1,0); seg tmp; int x; while(!Q.empty()){ tmp = Q.top(), Q.pop(); x = find(tmp.l); if(x<=tmp.r) invite(x,tmp.v), Q.push(tmp); } if(sum==A+B) printf("%lld\n",ans); else printf("-1\n"); return 0; }