题解 P3358 【最长k可重区间集问题】

皎月半洒花

2020-03-18 08:31:10

题解

感觉此题大部分题解都一点也不负责任。

本人十分不赞同,对于一道网络流题,一篇题解只说怎么建图不说思路的行为。我就想请问,难道做一道题的目的,就只在于做这一道题吗?A了就好?原理思路什么爱管不管?这能叫题解?

网络流题的重点就是在于建图。如果这都能一笔带过,那还做个锤子?

毫不夸张的说,如果张三做网络流题的时候就这么似是而非,就算他勤勤恳恳地做完了24题,省选的时候也运气好遇到了网络流题,做出来的概率绝对不高,或者说就根本不可能做出来。

你说,网络流题是什么?到现在为止还只是一个靠经验和阅历通杀的题目类别。所以网络流题需要格外重视经验的积累,知其然知其所以然。

总之呢,一道网络流题,不仔细分析思路直接告诉你怎么建图,就是在耍流氓。

给定实直线 L n 个开区间组成的集合 I ,和一个正整数 k ,试设计一个算法,从开区间集合 I 中选取出开区间集合 S \subseteq I ,使得在实直线 L 的任何一点 x S 中包含点 x 的开区间个数不超过 k 。且 \sum\limits_{z \in S} | z | 达到最大。这样的集合 S 称为开区间集合 I 的最长 k 可重区间集。 \sum\limits_{z \in S} | z | 称为最长 k 可重区间集的长度。

对于给定的开区间集合 I 和整数 k ,计算开区间集合 I 的最长 k 可重区间集的长度。

1 \leq n \leq 500, 1 \leq k \leq 3

首先一拿出来,这不就是匹配问题嘛?一个点最多匹配 k 个区间。于是每个位置建一个点,然后连向覆盖自己的点,然后…好像不太对?其一他没给下标的取值范围,其二一个线段覆盖多个点,要么都覆盖要么都不覆盖,这个限制很难表示…

于是好像不知道从何处入手了。发现一个这样的性质,就是永远不会选择 k 个以上交于 1 点的区间。也就是说,如果两个区间彼此之间没有交,就可以同时选;否则能不能同时选,看情况。这像极了「限制」,也就是如果两个区间之间没有交,那两者不存在限制;否则存在 k 的限制。

根据一开始的匹配,可以猜到大致上用网络流是可行的。并且似乎网络流很适合用流量来表征限制。那么考虑,如果两个区间不存在限制,那么应该怎么办——网络流类似电流,所以此时如果串联的话,就代表着可以同时选;那么如果存在限制,就意味着不能串联。根据这一点,考虑如何串联。发现本质上是将两个不交的区间中间连 f=\infty,c=0 的边。

这一点就引申出两个建图方法,其本质是相同的:

1、建立一个超级源 \rm S 和一个源 \rm S' ,中间连 f=k,c=0 的边,目的是提供初始流量。\rm S' 向每个区间的左端点连一条 f=1,c=-1 边。然后区间左端点向右端点连边 f=1,c=-len 表示贡献,每个右端点再向 \rm T 连边即可。如果两个区间不交,就由一个区间的 r 连向另一个区间的 l (当然要按秩啦)。思考这样做的合理性,对于相交的区间,一定是并联;否则的话就是串联(其实叫做混连,但是问题不大)。

2、建立一个源 \rm S 连向数轴上的 0 位置,f=k,c=0。然后数轴上每个 i>0i+1 连边 f=k,c=0。最后 maxright\rm T 连边。对于一个区间,连法跟1相同。

注意:

1、为什么要拆点?此处拆点的作用值得注意。对于一个区间,本质上应该抽象成一个点。但是在流图里是不存在「点权」这个概念的。所以需要把点权转边权,拆点的作用便在于此。

2、其实上面两个方法,可以通过初中物理里面什么「判断两个电路图是否等价」的知识来解决的233

3、由于本题保证了「开区间」,所以可以直接 l\to r,len=r-l 。当然如果是闭区间,只需要改成 l\to r+1 即可。

4、上面的第二个方案,发现最终可能存在很多数轴上的点 i 只与 i-1,i+1 连了 f=k,c=0 的边,所以是没用的,离散化掉就好了。

const int N = 200010 ;
const int I = 998244353 ;

#define ft first
#define sc second
#define to(k) e[k].to
#define fr(k) e[k].from
#define fw(k) e[k].flow
#define ct(k) e[k].cost
#define next(k) e[k].next
#define pint pair<int, int>

struct Edge{
    int to ;
    int from ;
    int flow ;
    int cost ;
    int next ;
}e[N * 2] ;

int _s ;
int _t ;
int ans ;
int res ;
int cnt ;
int n, m ;
int g[N] ;
int vis[N] ;
int dis[N] ;
int pre[N] ;
int head[N] ;
int _last[N] ;
queue <int> q ;

void add(int u, int v, int f, int c){
    to(++ cnt) = v ;
    next(cnt) = head[u] ;
    fw(cnt) = f ; ct(cnt) = c ;
    fr(cnt) = u ; head[u] = cnt ;
}
bool spfa(){
    fill(g, g + n + 1, I) ;
    fill(dis, dis + n + 1, I) ;
    fill(vis, vis + n + 1, 0) ;
    q.push(_s) ; vis[_s] = 1 ; dis[_s] = 0 ;
    while (!q.empty()){
        int x = q.front() ;
        q.pop() ; vis[x] = 0 ;
        for (int k = head[x] ; ~k ; k = next(k))
            if (dis[to(k)] > dis[x] + ct(k) && fw(k)){
                dis[to(k)] = dis[x] + ct(k) ;
                g[to(k)] = min(fw(k), g[x]) ;
                pre[to(k)] = x ; _last[to(k)] = k ;
                if (!vis[to(k)]){
                    q.push(to(k)) ;
                    vis[to(k)] = 1 ;
                }
            }
    }
    return (bool)(dis[_t] < I) ;
}
void ek(){
    while (spfa()){
        int x = _t ;
        res += g[_t] ;
        ans += g[_t] * dis[_t] ;
        //cout << g[_t] << " " << ans << endl ;
        while (x != _s){
            fw(_last[x]) -= g[_t] ;
            fw(_last[x] ^ 1) += g[_t] ;
            x = pre[x] ;
        }
    }
}

int len[N] ;
pint base[N] ;
int _n, _k, tot ;
map <int, int> Id, buc ;
map <int, int> :: iterator t ;

int main(){
    int u, v, f, c ;
    cin >> _n >> _k ; cnt = -1 ;
    memset(head, -1, sizeof(head)) ;
    for (int i = 1 ; i <= _n ; ++ i){
        cin >> base[i].ft >> base[i].sc ;
        len[i] = base[i].sc - base[i].ft ;
    }
    for (int i = 1 ; i <= _n ; ++ i){
        if (!Id.count(base[i].ft)) buc[base[i].ft] ++ ;
        if (!Id.count(base[i].sc)) buc[base[i].sc] ++ ;
    }
    add(0, 1, _k, 0) ; add(1, 0, 0, 0) ;
    for (t = buc.begin() ; t != buc.end() ; ++ t)
        Id[t->ft] = ++ tot ; _s = 0 ; _t = tot + 1 ;
    for (int i = 1 ; i <= tot ; ++ i)
        add(i, i + 1, I, 0), add(i + 1, i, 0, 0) ;
    for (int i = 1 ; i <= _n ; ++ i){
        //cout << Id[base[i].ft] << " " << Id[base[i].sc] << endl ;
        add(Id[base[i].ft], Id[base[i].sc], 1, -len[i]) ;
        add(Id[base[i].sc], Id[base[i].ft], 0, len[i]) ;
    }
    n = _t + 1 ; ek() ;
    cout << -ans << endl ;
}