Topcoder Srm 726 Div1 Hard
解题思路:
问题可以看做一个二分图,左边一个点向右边一段区间连边,匹配了左边一个点就能获得对应的权值,最大化所得到的权值的和。
然后可以证明一个结论,将点按照权值大小排序后,从大到小加点的充要条件是完美匹配大小 \(+1\) 。考虑如果不是按照这种方式加点的,必然能找到一个没有被匹配的点替换掉一个在匹配中但是权值比它小的点,答案一定会变大。
于是我们可以从大到小枚举点,如果能加进去且完美匹配大小增加就加入这个点,否则就不加。
solutoin1:
一个左侧点能被加进去当且仅当其能找到一条增广路,考虑暴力维护右侧所有在区间内的点出发能否找到增广路,如果可以就等价于当前左边的点是否能和这个右侧点匹配且完美匹配大小增加。
如果这个左侧点被加进去,那么找一个可以合法匹配的右侧点匹配,并减小此条增广路上的流量,考虑任意时刻左边的点数都不会大于右边的点数,所以直接重新BFS一遍求出哪些右侧点可以通过有流量的路径到 \(T\) 即可。
考虑如果有多个右侧的点能和这个左侧的点合法匹配,那么只需要随便选一个即可,因为反向边的存在,选其中任意一个它都可以通过反向边到达其它的右侧点。
这样前面检查的是否有增广路的复杂度是 \(m\) ,后面重新维护的的复杂度是 \(m^2\) ,由于前面的操作最多做 \(n\) 次,后面的操作最多做 \(m\) 次,总复杂度 \(O(nm+m^3)\)。
考虑用一个线段树优化一下建图,那么可以 \(logm\) 判断这一个左侧点是否能被合法匹配,每次重新维护的复杂度变成 \(mlogm\) ,总复杂度优化到 \(O((n+m^2)logm)\)。
Solution2:
扩展一下Hall定理可以得到一个结论,如果要存在完美匹配,当且仅当每一个右侧的区间 \([l ,r]\) 。都要满足
\]
其中 \(tot\) 是左边的点向右侧连边的区间被 \([l,r]\) 完全覆盖的数量。
于是可以维护出每一个区间还能接受的段的数量,每次加入一个点判断所有覆盖它的 \([l,r]\) 是否都还能接受即可。
相当于要维护一个二维平面上从右上角出发的矩形的最值查询和区间减法,用线段树维护一下即可,复杂度也是 \(O((n+m^2)logm)\)。
code (solution1)
/*program by mangoyang*/
#pragma GCC optimize("Ofast", "inline")
#include<bits/stdc++.h>
#define inf (0x7f7f7f7f)
#define Max(a, b) ((a) > (b) ? (a) : (b))
#define Min(a, b) ((a) < (b) ? (a) : (b))
typedef long long ll;
using namespace std;
template <class T>
inline void read(T &x){
int f = 0, ch = 0; x = 0;
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
if(f) x = -x;
}
const int N = 4000005, T = N - 1;
queue<int> q;
vector<int> d;
int a[N], cap[N], nxt[N], head[N], cnt;
int vis[N], b[N], s[N], size, n, m, ans;
inline void Addedge(int x, int y, int z){
a[++cnt] = y, cap[cnt] = z, nxt[cnt] = head[x], head[x] = cnt;
a[++cnt] = x, cap[cnt] = 0, nxt[cnt] = head[y], head[y] = cnt;
}
namespace Seg{
int ok[N], lc[N], rc[N], root;
inline void build(int &u, int l, int r){
u = n + (++size), ok[u] = 1;
if(l == r)
return (void) (b[u] = l, Addedge(u, T, 1));
int mid = l + r >> 1;
build(lc[u], l, mid), build(rc[u], mid + 1, r);
Addedge(u, lc[u], inf);
Addedge(u, rc[u], inf);
}
inline void addedge(int u, int l, int r, int L, int R, int x){
if(l >= L && r <= R)
return (void) (Addedge(x, u, inf));
int mid = l + r >> 1;
if(L <= mid) addedge(lc[u], l, mid, L, R, x);
if(mid < R) addedge(rc[u], mid + 1, r, L, R, x);
}
inline void rebuild(int u, int l, int r, int a[]){
if(l == r) return (void) (ok[u] = a[l]);
int mid = l + r >> 1;
rebuild(lc[u], l, mid, a);
rebuild(rc[u], mid + 1, r, a); ok[u] = ok[lc[u]] | ok[rc[u]];
}
inline int query(int u, int l, int r, int L, int R){
if(l >= L && r <= R) return ok[u];
int mid = l + r >> 1, res = 0;
if(L <= mid) res |= query(lc[u], l, mid, L, R);
if(mid < R) res |= query(rc[u], mid + 1, r, L, R);
return res;
}
}
inline bool dfs(int u){
if(u == T) return 1; vis[u] = 1;
for(int p = head[u]; p; p = nxt[p])
if(cap[p] > 0 && !vis[a[p]] && dfs(a[p]))
return cap[p]--, cap[p^1]++, 1;
return 0;
}
inline void bfs(){
q.push(T), vis[T] = 1;
for(int i = 0; i < d.size(); i++) vis[d[i]] = 0;
for(int i = 1; i <= size; i++) vis[i+n] = 0;
for(; !q.empty(); q.pop()){
int u = q.front();
for(int p = head[u]; p; p = nxt[p]) if(cap[p^1] > 0)
if(!vis[a[p]]) vis[a[p]] = 1, q.push(a[p]);
}
for(int i = 1; i <= size; i++) if(b[n+i]) s[b[n+i]] = vis[n+i];
Seg::rebuild(Seg::root, 1, m, s);
for(int i = 0; i < d.size(); i++) vis[d[i]] = 0;
for(int i = 1; i <= size; i++) vis[i+n] = 0; vis[T] = 0;
}
struct Node{ int l, r, w; } p[N];
inline bool cmp(Node A, Node B){ return A.w > B.w; }
int main(){
cnt = 1; read(n), read(m);
for(int i = 1; i <= n; i++)
read(p[i].l), read(p[i].r), read(p[i].w);
sort(p + 1, p + n + 1, cmp);
Seg::build(Seg::root, 1, m);
for(int i = 1; i <= n; i++){
if(p[i].l > p[i].r) continue;
if(!Seg::query(Seg::root, 1, m, p[i].l, p[i].r)) continue;
ans += p[i].w, Seg::addedge(Seg::root, 1, m, p[i].l, p[i].r, i);
d.push_back(i), dfs(i), bfs();
}
cout << ans;
}