1 条题解

  • 0
    @ 2026-8-6 23:52:17

    我们从最简单的方式进行考虑。

    我们定义 dpi,j,k,s=0/1dp_{i, j, k, s} = 0/1 表示在打第 ii 关,用了 kk 次第 jj 把武器后,还剩 ss 的金币数,是否可以从边界情况转移过来。

    这个边界情况就是 dp0,0,0,x=1dp_{0, 0, 0, x} = 1

    那么肯定会从 dpi1,j,k1,sridp_{i-1, j, k-1, s-r_i} 转移过来。同时,还需要枚举上一把可能的剑是什么,以及用了多少次,将这些的 dp 的值或起来就行。需要注意的是,在枚举的同时还需要判断剑是否能杀死怪物。

    当然,这个的时间复杂度为 O(Vn5)O(Vn^5),可以得到样例分。

    一种最经典的降维方式就是把金币数量变为 dp 的答案。

    我们可以这样定义,dpi,j,k=sdp_{i, j, k} = s 表示在打第 ii 关,用了 kk 次第 jj 把武器后,还剩的金币数量最多的是 dpi,j,kdp_{i, j, k}

    这样,就可以将上面的或运算转化为 max 运算。

    现在优化掉了 O(V)O(V),但是还是只能的样例分。

    肯定是要降维的,所以直接考虑将第 33 维优化掉。

    这时候,就不能只靠上一个 ii 转移了,需要的是 [max(0,ik),i)[\max(0, i-k),i) 之间的数进行转移。

    转移方程大致如下,后面还有一些约束条件:

    $$dp_{i, j} = \max_{\max(i-k, 0) \le t < i, 0 \le p \le m} dp_{t, p} + s_i - s_t - c_j$$

    这个 ss 表示的是 aa 的前缀和。

    然后,tt 还需要满足 maxl=t+1ihl>sj,dpt,p<cj\max_{l=t+1}^i h_l > s_j,dp_{t, p} < c_j,而 pp 需要满足 p=0p = 0tt 也必须为 00

    具体代码如下:

    for (int i = 1;i<= n;i++) {
    		for (int j = 1;j<= m;j++) {
    			
    			for (int t = max(i-k, 0ll);t< i;t++) {
    				if (doQueryMax(1, 1, n, t+1, i) > c[j].a) continue; // 线段树实现查询区间最大
    				for (int p = 0;p<= m;p++) {
    					if (p == 0 && t != 0) continue;
                        if (dp[t][p] < c[j].b) continue;
    					dp[i][j] = max(dp[i][j], dp[t][p] + psum[i] - psum[t] - c[j].b);
    				}
    			}
    		}
    	}
    

    考虑继续降维,我们可以清晰的发现 jj 没有被转移来的 dp 所用,只用在一个定值上。

    所以就可以得出 dpidp_i 表示打完第 ii 轮后,最多有多少金币。

    转移方程大致如下,边界跟上面的差不多:

    dpi=maxjdpj+sisjf(j+1,i)dp_i = \max_{j} dp_j + s_i - s_j - f(j+1, i)

    其中 f(x,y)f(x, y) 表示在区间 [x,y][x, y] 中,怪兽的生命值最大为 pp,找到一把剑使得 s>ps > p,且需要的金币最少。

    这样 O(n2)O(n^2) 的算法就出来了,可以成功的得到 3535 分。

    现在也降不了维了,只能考虑优化时间复杂度。

    考虑使用线段树维护,但是 ff 好像并不好直接维护,每个位置的 ff 都是在变的。同时,我们还需要满足 dpj>f(j+1,i)dp_j > f(j+1, i) 这一条件。

    但是呢,我们发现这是一个单调不递减的东西,而每次加了一个值,可能会覆盖前面的某些值来满足单调性。

    这个就很像单调栈了,每个元素记录管辖区间,生命值,和 ff 的值,进行修改即可。

    这个地方就可以使用平衡树或势能线段树来做了。

    理论上,最坏的时间复杂度应该是 O(nlog2(n))O(n\log^2(n))

    写代码的时候,特别是写单调栈部分,多多注意一些情况。

    可能还有些问题:

    如果存在一个位置不满足 dpi>f(x,y)dp_i > f(x, y),经过后面的添加,可能会满足条件吗?

    不会,因为这个是一个单调的东西,添加一个数,要么不变,要么变大。

    思路在于解释,实现在于代码,如果不懂如何写势能线段树,可以看一下代码。

    #include <bits/stdc++.h>
    #define int long long
    using namespace std;
    
    const int N = 5e5+10;
    const int inf = 0x3f3f3f3f3f3f3f3f;
    
    struct node {
    	int a, b;
    } c[N];
    
    struct Tuple {
    	int first, second, third;
    };
    
    int n, m, k, x;
    
    int a[N], b[N];
    
    int dm;
    int d[N*2];
    
    int psum[N];
    
    int dp[N];
    
    int top;
    Tuple st[N];
    
    // 第一维就是怪兽的生命值,第二维是这个所覆盖区间的左端点,第三维是 f 函数的值
    
    void read(int &x) {
    	int f = 1; x = 0; char ch = getchar_unlocked();
    	while (!(ch >= '0' && ch <= '9')) {
    		if (ch == '-') f = -1;
    		ch = getchar_unlocked();
    	}
    	while (ch >= '0' && ch <= '9') {
    		x = x * 10 + (ch - '0');
    		ch = getchar_unlocked();
    	} x *= f;
    }
    
    class Beats { // 势能线段树
    
        /*
        我们记录 ma 和 mi
    
    
        当我们减掉 f 函数值是就需要判断 mi - f 是否小于 0,这里实现的是 doChage 部分
    
        小于 0 就可以还原了
    
        有些大于 0 的,是通过判断排除的,详见 del
    
        因为 doChange 部分有判断 mi-f 的,所以只能再写一个单修的了
            
        */
        
    	private:
    		int tag[4*N], ma[4*N], mi[4*N];
    	public:
    		
    		void del(int k, int l, int r) {
    			if (mi[k] >= 0 || ma[k] < -inf / 2) return ; // 保证时间复杂度正确
    			if (l == r) { // 还原
    				ma[k] = -inf;
    				mi[k] = inf;
    				return ;
    			}
    			pushDown(k);
    			int mid = (l + r) >> 1;
    			del(k*2, l, mid), del(k*2+1, mid+1, r);
    			ma[k] = max(ma[k*2], ma[k*2+1]);
    			mi[k] = min(mi[k*2], mi[k*2+1]);
    		}
    		
    		void doChangeOne(int k, int dx) {
    			tag[k] += dx, ma[k] += dx, mi[k] += dx;
    		}
    		
    		void pushDown(int k) {
    			doChangeOne(k*2, tag[k]);
    			doChangeOne(k*2+1, tag[k]);
    			tag[k] = 0;
    		}
    		
    		void doChange(int k, int l, int r, int x, int y, int dx) {
    			if (r < x || y < l) return ;
    			if (x <= l && r <= y) {
    				doChangeOne(k, dx);
    				if (mi[k] < 0) del(k, l, r);
    				return ;
    			}
    			pushDown(k);
    			int mid = (l + r) >> 1;
    			doChange(k*2, l, mid, x, y, dx);
    			doChange(k*2+1, mid+1, r, x, y, dx);
    			ma[k] = max(ma[k*2], ma[k*2+1]);
    			mi[k] = min(mi[k*2], mi[k*2+1]);
    		}
    		
    		void doChangePoint(int k, int l, int r, int x, int dx, int dy) {
    			if (r < x || x < l) return ;
    			if (l == r) {
    				ma[k] = dx, mi[k] = dy;
    				tag[k] = 0;
    				return ;
    			}
    			pushDown(k);
    			int mid = (l + r) >> 1;
    			doChangePoint(k*2, l, mid, x, dx, dy);
    			doChangePoint(k*2+1, mid+1, r, x, dx, dy);
    			ma[k] = max(ma[k*2], ma[k*2+1]);
    			mi[k] = min(mi[k*2], mi[k*2+1]);
    		}
    		
    		int doQueryMax(int k, int l, int r, int x, int y) {
    			if (r < x || y < l || x > y) return -inf;
    			if (x <= l && r <= y) return ma[k];
    			pushDown(k);
    			int mid = (l + r) >> 1;
    			return max(doQueryMax(k*2, l, mid, x, y), doQueryMax(k*2+1, mid+1, r, x, y));
    		}
    		
    } yjmiao;
    
    class soilder {
        private:
        
            int mi[4*N];
        
        public:
            void doBuild(int k, int l, int r) {
            	if (l == r) {
            		mi[k] = c[l].b;
            		return ;
            	}
            	int mid = (l + r) >> 1;
            	doBuild(k*2, l, mid);
            	doBuild(k*2+1, mid+1, r);
            	mi[k] = min(mi[k*2], mi[k*2+1]);
            }
    
            int doQueryMin(int k, int l, int r, int x, int y) {
            	if (r < x || y < l || x > y) return inf;
            	if (x <= l && r <= y) return mi[k];
            	int mid = (l + r) >> 1;
            	return min(doQueryMin(k*2, l, mid, x, y), doQueryMin(k*2+1, mid+1, r, x, y));
            }
    } zpmiao;
    
    __inline bool cmp(node x, node y) {
    	return x.a < y.a;
    }
    
    __inline int lower(int l, int r, int x) {
    	int ans = r + 1;
    	while (l <= r) {
    		int mid = (l + r) >> 1;
    		if (c[mid].a >= x) {
    			ans = mid, r = mid - 1;
    		} else l = mid + 1;
    	}
    	return ans;
    }
    
    __inline void solve() {
    	dp[0] = x;
    	yjmiao.doChangePoint(1, 0, n, 0, x, x);
    
        // 栈的定义见上方定义数组处
        
    	for (int i = 1;i<= n;i++) {
    		st[++top] = {0, i-1, 0}; // 一定要有这个~ 原来的栈右端点是 i-2 没有 i-1 所以需要新加一个
    		int last = i-1;
    		while (top > 0 && st[top].first <= a[i]) {
    			yjmiao.doChange(1, 0, n, st[top].second, last, st[top].third); // 还原,原来是减法嘛 现在还原就是用加法呗~
    			last = st[top].second-1;
    			top--;
    		}
    		int id = lower(1, m, a[i]);
    		int d = zpmiao.doQueryMin(1, 1, m, id, m); 
    		st[++top] = {a[i], last+1, d};
    		yjmiao.doChange(1, 0, n, st[top].second, i-1, -d); // 重新覆盖啦~
            
    		dp[i] = yjmiao.doQueryMax(1, 0, n, max(i-k, 0ll), i-1) + psum[i];
    		yjmiao.doChangePoint(1, 0, n, i, dp[i] - psum[i], dp[i]);
    	}
    	if (dp[n] >= 0) printf("Yes\n");  
    	else printf("No\n");
    	return ;
    }
    
    signed main() {
        int Max = -1;
    	read(n), read(m), read(k), read(x);
    	for (int i = 1;i<= n;i++) {
    		read(a[i]), read(b[i]);
    		d[++dm] = a[i];
    		psum[i] = psum[i-1] + b[i];
    	}
    	for (int i = 1;i<= m;i++) {
    		read(c[i].a), read(c[i].b);
    		d[++dm] = c[i].a;
    	}
    
        // 将 h 和 s 数组离散化
        sort(d+1, d+dm+1);
    	dm = unique(d+1, d+dm+1)-d-1;
    	for (int i = 1;i<= n;i++) {
    		a[i] = lower_bound(d+1, d+dm+1, a[i])-d;
            Max = max(Max, a[i]);
    	}
        for (int i = 1;i<= m;i++) {
            c[i].a = lower_bound(d+1, d+dm+1, c[i].a)-d;
        }
        
    	sort(c+1, c+m+1, cmp);
    	zpmiao.doBuild(1, 1, m);
        if (Max > c[m].a) {
            printf("No\n");
            return 0;
        }
    	solve();
    	return 0;
    }
    

    略微卡常即可。

    • 1

    信息

    ID
    12581
    时间
    1000ms
    内存
    700MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者