[BZOJ] 3126: [Usaco2013 Open]Photo
3126: [Usaco2013 Open]Photo
Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 318 Solved: 162
[Submit][Status][Discuss]
Description
Farmer John has decided to assemble a panoramic photo of a lineup of his N cows (1 <= N <= 200,000), which, as always, are conveniently numbered from 1..N. Accordingly, he snapped M (1 <= M <= 100,000) photos, each covering a contiguous range of cows: photo i contains cows a_i through b_i inclusive. The photos collectively may not necessarily cover every single cow. After taking his photos, FJ notices a very interesting phenomenon: each photo he took contains exactly one cow with spots! FJ was aware that he had some number of spotted cows in his herd, but he had never actually counted them. Based on his photos, please determine the maximum possible number of spotted cows that could exist in his herd. Output -1 if there is no possible assignment of spots to cows consistent with FJ's photographic results.
给你一个n长度的数轴和m个区间,每个区间里有且仅有一个点,问能有多少个点
Input
* Line 1: Two integers N and M.
* Lines 2..M+1: Line i+1 contains a_i and b_i.
Output
* Line 1: The maximum possible number of spotted cows on FJ's farm, or -1 if there is no possible solution.
Sample Input
1 4
2 5
3 4
INPUT DETAILS: There are 5 cows and 3 photos. The first photo contains cows 1 through 4, etc.
Sample Output
OUTPUT DETAILS: From the last photo, we know that either cow 3 or cow 4 must be spotted. By choosing either of these, we satisfy the first two photos as well.
HINT
Source
Analysis
这翻译质量不敢恭维= =最后还是自己看了原文
= ,=
FJ对着一队奶牛拍了一堆照片
巧合的是,每张照片中都恰好有一只奶牛
现在给你每张照片里最左边的奶牛和最右边的奶牛的编号
问你在已知条件下最多能有多少奶牛?
= 。=
这道题有点毒
先说分类:DP,单调队列优化DP
合理性:
状态表示为 F[ i ],表示只考虑前 i 只奶牛,且第 i 只奶牛必取,特殊情况下对 F[ i ] 取特殊值以区分
显然这样是没有后效性的
因此整个主线就基本明朗了:后面的继承前面的
那么就有一个被继承的区域,我们在这个区域里面选择最大值继承
好吧,根据题目,我们知道
每张照片里面 仅有且必有 一只有斑点的奶牛
那么我们继承就有这么个原则了:
1. 每个区间都必须要取一个点,可以共用;
2. 每个区间内不能重复取点
那么继承从距离 i 当前区间最近的左边的那个区间开始,直到 i 当前这个区间的最左的左端点结束
你看,点 i 可以是被多个区间所包围的。
我们所说的左边最近的区间,这个时候 i 的区间指的是最小的 i 的区间,也就是上图包括 i 点的最顶上的那个小区间
那么对 i 而言,这个左边最近的区间指的是图片最左端这块飞地;而对于 i‘ ,这个左边最近指的就是 i 的区间了
在继承过程中,我们需要考虑到每一个区间,因为每一张照片都必有一个奶牛,如果这里不是最小的区间的话,很可能会漏掉某些区间
而对于最左的左端点则是包围 i 点的最大的区间的左端点,因为在一个区间里只能取一个点,而被继承的那个点是必取的
所以我们得到了百度到的一堆题解的那个核心结论:
对于点 i ,它的继承区间是:从 所在区间左边最近的区间的左端点 起,至 所在区间的最左的左端点-1 结束
对于一个点怎么迅速找到它的继承区间呢?
这= =
显然看解题人数感了
我只能做到解释其合理性
1 for(int i = 1;i <= n+1;i++) r[i] = i-1; 2 3 for(int i = 1;i <= m;i++){ 4 scanf("%d%d",&a,&b); 5 r[b] = min(r[b],a-1); 6 l[b+1] = max(l[b+1],a); 7 } 8 9 for(int i = n;i;i--) 10 r[i] = min(r[i],r[i+1]); 11 12 for(int i = 2;i <= n+1;i++) 13 l[i] = max(l[i],l[i-1]);
读入一段区间时,我们就可以更新至少两个点的继承区间值了:b+1 和 b
对于b+1,无疑区间[a,b]是它左边最近的一个区间,可以更新它的继承区间左端点;
对于b,还有[a,b]里的所有的点,他们的继承区间右端点都可以以a-1为值更新,但是这里只更新b一个点,事后可以O(n)
那么对于每个点的继承区间右端点,我们从右往左扫意味着一件事情:右边的点可以改变左边,左边的点不能更新右边
其中红线蓝线都是B的待选区间,不同时存在
当B的区间是红线的时候,B可以继承红线和黑线不重叠的地方
而A的继承区间会终止于黑线后,结果对的!
当B的区间是蓝线的时候,显然A会拿B的取值去更新,也是对的!
对于继承区间的左端点的O(n)更新,和右端点取值同理,自行思考即可
巧妙运用了某些值取值的单调性
qwq这才是算法功底好的体现啊
那么在DP的时候我们的方向就很明确了:
对于每个点,从它的继承区间里继承就行了,需要不停地扫,目测n方
而我们要取的是区间里的最大值
那么这个时候滑动窗口就发挥作用了:
如果当前这个考虑的被继承点 j 的DP值小于我们已经知道的最大值,就没必要考虑了
而对于那些已经不在继承区间里却仍然被保存的值,也没必要考虑了
因此来一波单调队列,在代码里是单调队列维护最大值,这个解释清楚了估计就很容易理解了
现在来考虑非法情况,还是就上面那张图片:
非法情况,就是 i 和 i’ 这样子的,两个区间被一个大区间包括,那么这个大区间里必须要选两个点
这个时候,我们把该点置为非法,也就是-inf
并且从这个第一次出现非法情况的 i‘ 所在的区间开始,之后每一个点都会是非法点,这个非法值会延续到最后一个点
因为对于处于大区间的点,非法区间及以后的点他们的继承区间左右端点是非法的,右端点小于左端点,包含了一个空集
你看,i’ 区间内的点,右端点是大区间的左端点-1,左端点却是 i 区间的左端点
而出了 i‘ 区间,右端点依然是大区间的左端点,左端点是 i’ 的左端点,持续非法
而出了大区间,第一个点是大区间右端点+1,叫他A点
A点的左端点是 i‘ 左端点,右端点根据初始化是A-1,而这里所有的值都是非法值
之后也显然没法再取到一个正常的值了
综上可证该算法中非法值有持续性和覆盖性
另外再说结尾是n+1的原因
我们对DP[ i ] 的定义是必取 i 的情况
但是我们不一定取 n 为斑点奶牛
所以我们要选择n+1
我怎么感觉我写的东西结果就是一堆废话qwq
Code
1 /************************************************************** 2 Problem: 3126 3 User: child 4 Language: C++ 5 Result: Accepted 6 Time:184 ms 7 Memory:4444 kb 8 ****************************************************************/ 9 10 #include<cstdio> 11 #include<iostream> 12 #define maxn 202020 13 using namespace std; 14 15 int n,m,a,b,r[maxn],l[maxn],DP[maxn]; 16 int que[maxn],head,tail; 17 18 int main(){ 19 scanf("%d%d",&n,&m); 20 21 for(int i = 1;i <= n+1;i++) r[i] = i-1; 22 23 for(int i = 1;i <= m;i++){ 24 scanf("%d%d",&a,&b); 25 r[b] = min(r[b],a-1); 26 l[b+1] = max(l[b+1],a); 27 } 28 29 for(int i = n;i;i--) 30 r[i] = min(r[i],r[i+1]); 31 32 for(int i = 2;i <= n+1;i++) 33 l[i] = max(l[i],l[i-1]); 34 35 for(int i = 1;i <= n+1;i++){ 36 for(int k = l[i];k <= r[i];k++){ 37 while(tail < head && DP[que[head]] <= DP[k]) head--; 38 que[++head] = k; 39 } 40 41 while(tail < head && que[tail+1] < l[i]) tail++; 42 43 if(tail == head) DP[i] = -1e9; 44 else DP[i] = DP[que[tail+1]] +1; 45 } 46 47 // for(int i = 0;i <= n+1;i++) printf("%d ",DP[i]); 48 // cout << endl; 49 50 if(DP[n+1] >= 0) printf("%d\n",DP[n+1]-1); 51 else printf("-1\n"); 52 53 return 0; 54 }