扫描线优化 DP 与单调队列优化 DP

扫描线优化 DP

也是是列出暴力 DP 转移后发现可以用扫描线优化。

P3431 [POI 2005] AUT-The Bus

题目大意:有一个 \(n\times m\) 的网格,上面有 \(k\) 个点,每个点有权值 \(p_i\),每次只能向上或向右走,求经过的点的权值和的最值。

\(1\le n,m \le 10^9\),\(1\le k\le 10^5\)

依旧考虑暴力转移。设 \(dp[i]\) 为走到第 \(i\) 个点时,经过的点的权值和的最值。

\(dp_i=\max\{f_j\}+p_i\)(\(x_j\le x_i\) 且 \(y_j\le y_i\))

时间复杂度 \(O(k^2)\),会炸。

发现 \(\max\{f_j\}\) 就是一个标准的二维数点问题,可以使用扫描线维护。

课件里还说可以用树状数组,但我不会。

\(O(k\log(k))\) 做法:先排个序,然后用扫描线维护 \((0,0)\) 到 \((x_i,x_i)\) 的 DP 最大值,然后把新的 \(dp_i\) 也接近扫描线中就可以了。

初始化:无

答案:\(\max\{dp_i\}\)(\(1\le i\le n\))

点击查看代码
#include<bits/stdc++.h>
using namespace std;
using lint= long long ;
int n,m,k;
struct POINT{
	int x,y;
	lint p;
	
	bool operator<(const POINT & a)const{
		if(y==a.y) return x<a.x;
		return y<a.y;
	}
};
int t[4000005],cnt=1,ls[4000005],rs[4000005],w[100005];
inline void pushup(int p){
	t[p]=max(t[ls[p]],t[rs[p]]);
}
void update(int &p,int l,int r,int q,int v){
	if(!p)p=++cnt;
	if(l==r){
		t[p]=max(t[p],v);
		return;
	}
	int mid=(l+r)>>1;
	if(q<=mid)update(ls[p],l,mid,q,v);
	if(q>mid)update(rs[p],mid+1,r,q,v);
	pushup(p);
}
int query(int &p,int l,int r,int ql,int qr){
	if(!p)p=++cnt;
	if(ql<=l&&qr>=r){
		return t[p];
	}
	int mid=(l+r)>>1,res=0;
	if(ql<=mid) res=max(res,query(ls[p],l,mid,ql,qr));
	if(qr>mid) res=max(res,query(rs[p],mid+1,r,ql,qr));
	return res;
}

int dp[100005];
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>m>>k;
	vector<POINT> p(k+10);
	for(int i=1;i<=k;i++) cin>>p[i].x>>p[i].y>>p[i].p;
	sort(p.begin()+1,p.begin()+k+1);
	int root=1;
	for(int i=1;i<=k;i++){
		dp[i]=query(root,1,1e9,1,p[i].x)+p[i].p;
		update(root,1,1e9,p[i].x,dp[i]);
	}
	cout<<query(root,1,1e9,1,1e9);
	return 0;
}

CF960F Pathwalks

题意简述
求一张图上边的编号以及边权全部严格单调递增的最长路径长度。

思路
这到题解决问题的对象是边,我们发现,点可以形成环,但是边不行。

所以我们考虑以边作为 DP 的对象。

\(dp_i\) 表示走到第 \(i\) 条边的时候的最长路径长度。
若有一条边 \(j\) 要转移到边 \(i\),则需要满足三个条件:

  • \(j<i\)

  • \(k_j<k_i\)

  • \(j\) 可以到达 \(i\)。

若不管条件三,那么这个问题就很容易解决,之前在线段树优化 DP 就讲过。

如果加上了条件三,可以对每个点建一颗线段树,然后动态开点解决(话说为啥在扫描线里面?)。

空间会不会炸?在动态开点的前提下,每次修改最多增加 \(\log{100}\) 个线段树节点,不会炸。

注意修改操作是和原来的值取更大值,不是直接赋值也不是直接累加(也是线段树优化 DP 的一个易错点)。

单调队列优化 DP

单调队列

就是一个保持单调性的双端队列,并且有限定大小,可以用来 \(O(n)\) 求限定大小区间最值。

oi-wiki

P1714 切蛋糕

我是洛谷最优解!
我是洛谷最优解!
我是洛谷最优解!
我是洛谷最优解!
我是洛谷最优解!
STY和CDR惜败,比我慢5ms。其实是快读速度比拼

形式化题意:在数列 \(\{p_n\}\) 中,找出一个子段 \([l,r](r-l+1\le m)\),最大化 \(\sum\limits_{i=l}^rp_i\)?

套路,先考虑暴力 DP 转移。

设 \(dp_i\) 为为以第 \(i\) 个元素结尾的最大子段和,\(sum\) 为前缀和。

\(dp_i=\max_{i−M≤j≤i−1}\{sum_i-sum_j\}\)

转化一下,等价于 \(dp_i = sum_i - \min_{1\le i<j\le n}\{ sum_j \}\)

注意到 \(\min_{1\le i<j\le n}\{ sum_j \}\) 可以用单调队列维护,然后就没了。

最优解代码(快读占90%,网上找的快读把getchar改为getchar_unlocked)
#include<bits/stdc++.h>
using namespace std;
int sum[500005];
int q[500005],head=1,tail=0;
// 究极快读
#define INPUT_OPTIMIZE
#define OUTPUT_OPTIMIZE
#define INLINE_OPTIMIZE
#define USING_MMAP
#define ONLY_BLANK
#ifdef USING_MMAP
 #ifdef __unix__
  #define UINT16_OPTIMIZE 
 #endif
#endif
namespace IO {
 constexpr int Read_Bufsize = 1 << 21 | 20;
 constexpr int Write_Bufsize = 1 << 21 | 20;
#ifdef INLINE_OPTIMIZE
 #define IOINLINE [[gnu::always_inline]] inline
#else
 #define IOINLINE inline
#endif
#define mmap_init() 0
#ifdef INPUT_OPTIMIZE
 #ifdef __unix__
  #ifdef USING_MMAP
   #include <fcntl.h>
   #include <sys/mman.h>
   #include <sys/stat.h>
   #include <unistd.h>
   #undef mmap_init
   constexpr int fd = 0;
   inline static const unsigned char *p1; struct stat stat_;
   IOINLINE void mmap_init() {
    fstat(fd, &stat_);
    p1 = (const unsigned char *)mmap(NULL, stat_.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
   }
   #define getchar_unlocked() (*p1++)
  #else
   static unsigned char buf[Read_Bufsize], *p1 = buf, *p2 = buf;
   #define getchar_unlocked() (p1 == p2 && (p2 = (p1 = buf) + fread_unlocked(buf, 1, Read_Bufsize, stdin), p1 == p2) ? EOF : *p1++)
  #endif
 #else
  static unsigned char buf[Read_Bufsize], *p1 = buf, *p2 = buf;
  #define getchar_unlocked() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, Read_Bufsize, stdin), p1 == p2) ? EOF : *p1++)
 #endif
#endif
#ifdef UINT16_OPTIMIZE
 struct uint16_table {
  int table[65540];
  constexpr uint16_table() : table{} {
   for(int i = 0; i < 65536; ++i) table[i] = -1;
   for(int i = 48; i != 58; ++i)
    for(int j = 48; j != 58; ++j)
     table[i << 8 | j] = (j & 15) * 10 + (i & 15);
  }
 };
 constexpr uint16_table Table;
 #define TABLE Table.table
#endif
 IOINLINE bool read(unsigned char *t) {
  memset(t, 0, sizeof t);
  unsigned char *pd = t, c = getchar_unlocked();
  while (isspace(c)) c = getchar_unlocked();
  while (!isspace(c)) *pd++ = c, c = getchar_unlocked();
  return c == EOF;
 }
 template <typename T> 
 IOINLINE bool read(T &t) {
  t=0;T sgn=1;
  int c = getchar_unlocked();
  #ifdef UINT16_OPTIMIZE
   #ifdef ONLY_BLANK
    if(c<=32) c = getchar_unlocked();
    if(c==45) sgn = -1, c = getchar_unlocked();
   #else
    while(!isdigit(c)) {if(c == 45) sgn *= -1; c = getchar_unlocked();}
   #endif
   #define xio_next __builtin_expect(!!~TABLE[*(uint16_t*)(p1 - 1)],1) && ((t = TABLE[*(uint16_t*)(p1 - 1)], p1+=2))
   #define io_next (~TABLE[*(uint16_t*)(p1 - 1)]) && ((t = t * 100 + TABLE[*(uint16_t*)(p1 - 1)], p1+=2))
   xio_next && io_next && io_next && io_next;
   c = *(p1 - 1);
   #ifdef ONLY_BLANK
    if(c>32) t = t * 10 + (c & 15), ++p1;
   #else
    if(isdigit(c)) t = t * 10 + (c & 15), ++p1;
   #endif
  #else
   #ifdef ONLY_BLANK
    while(c<=32) c = getchar_unlocked();
    if(c==45) sgn = -1, c=getchar_unlocked();
    while(c>32) t = t * 10 + (c & 15), c = getchar_unlocked();
   #else
    while(!isdigit(c)) {if(c == 45)sgn *= -1;c = getchar_unlocked();}
    while(isdigit(c)) t = t * 10 + (c & 15), c =getchar_unlocked();
   #endif
  #endif
  t*=sgn;
  return c == EOF;
 }
 template <typename T, typename... Args> 
 IOINLINE bool read(T &t, Args&... args) {
  return read(t) ? 1 : read(args...);
 }
#ifdef OUTPUT_OPTIMIZE
 static unsigned char outbuf[Write_Bufsize], *out = outbuf;
 #define putchar(x) (*out++ = x)
 #define flush() fwrite(outbuf, 1, out - outbuf, stdout)
 struct auto_flush { auto_flush() { atexit([]() {flush();}); } } initializer;
#else 
 #define flush() 0
#endif
 template <typename T> 
 IOINLINE void write(T x) {
  if (!x) return putchar(48), void();
  static unsigned char t[9], p = 0;
  while (x) t[++p] = (x % 10) & 15, x /= 10;
  while (p) putchar(t[p--]);
 }
} using namespace IO;
int main(){
	mmap_init();
    int n,k;
    read(n),read(k);
	int ans=-1000000000;
    q[++tail]=0;
    for(int i=1;i<=n;i++){
    	read(sum[i]),sum[i]+=sum[i-1];
        while(head<=tail && q[head]<i-k) head++;
        if(head<=tail) ans=max(ans,sum[i]-sum[q[head]]);
        while(head<=tail && sum[q[tail]]>=sum[i]) tail--;
        q[++tail]=i;
    }
    cout<<ans;
    return 0;
}

练习题:P1725 琪露诺,P10978 Fence

posted @ 2026-02-27 21:05  vivid/stasis  阅读(26)  评论(0)    收藏  举报