扫描线优化 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)\) 求限定大小区间最值。
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;
}

浙公网安备 33010602011771号