1. 程式人生 > 其它 >君君演算法課堂-好題分享1

君君演算法課堂-好題分享1

好題分享1

Problem Description

Tarzan 非常煩數軸因為數軸上的題總是難度非常大。不過他非常喜歡線段,因為有關線
段的題總是不難,諷刺的是在一個數軸上有 n 個線段,Tarzan 希望自己喜歡的東西和討厭的
東西不在一起,所以他要把這些線段分多次帶走,每一次帶走一組,最多能帶走 k 次。其實
就是要把這些線段分成至多 k 組,每次帶走一組,問題遠沒有那麼簡單,tarzan 還希望每次
選擇的線段組都很有相似性,我們定義一組線段的相似性是組內線段交集的長度,我們現在
想知道最多分成 k 個組帶走,Tarzan 最多能得到的相似性之和是多少?

Input format

第一行兩個整數 n 和 k。
接下來 n 行每行兩個整數 Li, Ri 表示線段的左右端點。

Output format

一行一個整數,表示最多能得到的相似性之和是多少。

Examples

input 1

5 3
5 10
4 11
6 9
10 30
20 40

output 1

43

input 2

5 3 
5 11
16 22
14 20
10 20
6 10

output 2

18

input 3

7 3 
1 9
2 9
2 10
5 15
3 14
14 18
16 20

output 3

21

Constrains and Notes

對於 20% 的資料滿足:\(n ≤ 8; k ≤ 5\)
對於 40% 的資料滿足:\(k, n ≤ 12\);
對於 70% 的資料滿足:\(n ≤ 100\)

,
對於 100% 的資料滿足:\(1 ≤ k ≤ n ≤ 6000, 1 ≤ Li < Ri ≤ 10^6\);

題解

對於 \(\%20\) 的資料,直接列舉每一個線段在哪一個集合裡面即可

時間複雜度:\(O(k^n)\)

對於 \(\%40\) 的資料,\(dfs\) 搜尋每一條線段在哪一個集合裡面,

進行適當剪枝後可以得到最高 \(40\) 分的部分分,

對於 \(\%70\) 的資料,首先,手玩樣例一番,收集性質如下:

①空集組(對答案無貢獻)最多隻有一組。

證明:若有\(k(k >= 2)\)組空集, 則在這些集合中找出前\(k-1\)條線段放入一個集合, 另外的線段放入剩下的一個集合可增加選出來的\(k-1\)

條線段長度的答案貢獻。

性質①得證。

②若沒有空集的話,可以再觀察到一個性質:

對於完全包含另一個線段B的線段A, 則B與A在一組可能會使答案更優(但不一定),不優的情況會在下文另行考慮。

證明:根據題意可得,對於給出的\(n\)條線段,每條線段都會屬於一個集合。

設長度為\(x\)的線段被長度為\(y(y > x)\)的線段包含。

\(x\)\(y\)包含,所以\(x\)\(y\)分到一組,則\(x\)所在集合對答案的貢獻最大為\(x\)

而若\(x\)與不包含\(x\)的線段分到一組時,在小的方面來說答案不優(整體來看可能更優)。

性質②得證。

下面對於性質②的缺陷作考慮:

性質②是將\(x\)\(y\)放到一個集合,那我們未考慮到的情況就是\(x\)\(y\)相分離的情況。

我們考慮\(x\)\(y\)相分離時怎樣最優。

顯然\(y\)單獨一個集合時對答案的貢獻最大。

當然,上面情況成立的條件是仍有空的集合未被使用。

注:當產生包含關係後,\(y\)對集合已無貢獻。

所以我們才可以將\(x\)\(y\)進行分離操作。

否則,演算法正確性無法得以保證。

此時,再結合性質①,對答案無貢獻的集合最多隻有一個。

我們便有了一種做法:

優先考慮不包含的情況,再將\(x\)\(y\)進行分離操作,更新答案。

因分離線段操作(只考慮包含別的線段的線段)不會對現有答案產生影響。

所以我們可以進行DP預處理操作。

將線段排序,挑選所有出\(r\)遞增且\(l\)遞增(不包含)的線段。

此時顯然能DP。

\(f[i][j]\)為前\(i\)條線段選了\(j\)個集合相似度之和。

注:這\(j\)個集合不能有空集,因為我們要給每個空集分發線段,更新答案。

否則答案不優。

考慮第\(j\)個集合放入第\(x+1\sim i\)條線段,

\(f[i][j]=max(f[x][j-1]+r[x+1]-l[i]|r[x+1]>l[i])\)

轉化方程得:\(f[i][j]=max(f[x][j-1]+r[x+1]|r[x+1]>l[i])-l[i]\)

對於 \(\%100\) 的資料,在 \(\%70\) 的資料的基礎上進行單調佇列優化。

考慮我們選擇了多少集合,設為\(p\),

考慮用包含其它線段的線段來更新答案。

則此情況對答案的貢獻為

\(f[n][p]\)+包含其它線段的前\(k-p\)長線段長度之和。

再列舉\(p\)取最優值即可。

時間複雜度:\(O(n^2)\),在空間上可以使用滾動陣列優化。

code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 6e3 + 5;
int read() {
	int x = 0, f = 1; char ch = getchar();
	while(! isdigit(ch)) f = (ch == '-') ? -1 : 1, ch = getchar();
	while(isdigit(ch)) x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
	return x * f;
}
struct edge { int l, r; } t1[N], t2[N];
int n, k, ans, r, len[N], q[N], head, tail, maxl, tot, cnt, f[2][N];
bool cop(const edge &a, const edge &b) { 
	return (a.r == b.r) ? (a.l > b.l) : (a.r < b.r);
}
int main() {
	n = read(); k = read();
	for(int i = 1; i <= n; i ++) 
		t1[i].l = read(), t1[i].r = read(), len[i] = t1[i].r - t1[i].l;
	sort(len + 1, len + n + 1);
	for(int i = n - k + 2; i <= n; i ++) ans += len[i];//共前 k-1 長 (總字首和); 
	sort(t1 + 1, t1 + n + 1, cop);
	memset(len, 0, sizeof(len));
	for(int i = 1; i <= n; i ++) {//保證r遞增 ;
		if(t1[i].l > maxl) {//若l遞增 (這些線段不可互相包含);
			t2[++ cnt] = t1[i];//加入t2(用於DP) ;
			maxl = t1[i].l;
		}
		else len[++ tot] = t1[i].r - t1[i].l;//r遞增l遞減,加入t1(說明這些線段有包含關係);
	}
	sort(len + 1, len + tot + 1, greater<int>());//長度從大到小排序 ;
	for(int i = 2; i <= n; i ++) len[i] += len[i-1];//求包含其它線段的線段的長度字首和;
	sort(t2 + 1, t2 + cnt + 1, cop);//這些線段不相互包含 ;
	r = 1;//滾動陣列優化;
	for(int i = 1; i <= cnt; i ++) {//保證r遞增(已經排序) (預處理操作);
		if(t2[1].r <= t2[i].l) f[0][i] = -1e9;//1號線段與i號線段為空集 ;
		else f[0][i] = t2[1].r - t2[i].l;//1號線段與i號線段有交集 ;
	}
	ans = max(ans, f[0][cnt] + len[k-1]);//1個集合 + (k - 1)個集合 ;
	for(int j = 2; j <= min(k, cnt); j ++, r ^= 1) {//列舉選出j個集合,(j < k);
		q[head = tail = 1] = 1; f[r][1] = -1e9;//滾動陣列清零 ;
		for(int i = 2; i <= cnt; i ++) {
			while(head <= tail&&t2[q[head]+1].r <= t2[i].l) head ++; //保證第j個集合對答案的貢獻為正;
			if(head <= tail) f[r][i] = f[r^1][q[head]]+t2[q[head]+1].r-t2[i].l;
			else f[r][i] = -1e9;
			while(head <= tail&&f[r^1][i] + t2[i+1].r >= t2[q[tail]+1].r + f[r^1][q[tail]]) tail --;//維護f[x][j-1]+r[x+1]的最大值;
			q[++ tail] = i;
		}
		ans = max(ans, f[r][cnt] + len[k - j]);//j個集合 + (k - j) 個集合 
	}
	cout << ans << endl;
	return 0;
}