1. 程式人生 > >bzoj 3126: [Usaco2013 Open]Photo (DP+單調佇列)

bzoj 3126: [Usaco2013 Open]Photo (DP+單調佇列)

題目描述

傳送門

題目大意:給你一個n長度的數軸和m個區間,每個區間裡有且僅有一個點,問能有多少個點

題解

想了各種不科學的貪心和亂搞,最終還是回到了DP上。
f[i]表示到第i個位置且第i個位置必放最多能放多少個點。
對於每個位置,他前一個能放置的位置應該是滿足一個區間的。
因為一個區間中只能有一個點,所以包含這個點的所有區間都不能再放,就是要找到包含這個點的區間中左端點最小的位置,R[i]=位置-1
因為每個區間都必須有一個點,所以不能有區間空著不放。找到整個區間完全在當前點之前且左端點最大的區間,L[i]=該區間的左端點。
怎麼統計L,R,每次加入一個區間[x,y],就用x更新L[y+1],x-1更新R[y].最後從小到大掃一遍,每個位置的L對字首最小值取max;從大到小掃一遍,每個位置的R對字尾最小值取min
那麼f

[i]=max{f[j],L[i]<=j<=R[i]}+1,可以用單調佇列優化。

程式碼

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 200003
using namespace std;
int n,m,L[N],R[N];
int head,tail,q[N],f[N];
int main()
{
    freopen("a.in","r",stdin);
    freopen("my.out"
,"w",stdout); scanf("%d%d",&n,&m); for (int i=1;i<=n+1;i++) R[i]=i-1; for (int i=1;i<=m;i++) { int x,y; scanf("%d%d",&x,&y); R[y]=min(R[y],x-1); L[y+1]=max(L[y+1],x); } for (int i=n;i>=1;i--) R[i]=min(R[i+1],R[i]); for (int i=2
;i<=n+1;i++) L[i]=max(L[i-1],L[i]); //for (int i=1;i<=n+1;i++) cout<<L[i]<<" "<<R[i]<<endl; int j=1; head=tail=1; q[1]=0; for (int i=1;i<=n+1;i++) { while (j<=R[i]&&j<=n) { if (f[j]==-1) { j++; continue; } while (f[j]>f[q[tail]]&&head<=tail) tail--; q[++tail]=j; j++; } while (q[head]<L[i]&&head<=tail) head++; if (head<=tail) f[i]=f[q[head]]+(i!=n+1?1:0); else f[i]=-1; } // for (int i=1;i<=n+1;i++) cout<<f[i]<<" "; cout<<endl; printf("%d\n",f[n+1]); }