CF1866D Digital Wallet

传送门

题意

给你一个\(n*m\)正数矩阵,(\(n\le 10, m \le 1e5, k\le10\)), 有一个\(n*k\)的窗口在矩阵中,\(k\leq m\), 这个窗口一开始在最左边,你可以从窗口覆盖的范围里取出一个数加入答案并置零, 接下来窗口会每次向右滑动一格,每次滑动完你都可以取一个数加入答案并置零, 直到窗口无法滑动时停止,问最大答案。

题解

简略题解: 我们考虑所有数是同时取的,对于每一列我们完全可以从大到小排序,这一列如果取了\(x\)个数,那一定是前\(x\)大的数。

于是问题转化为有若干个区间,每个区间可以选择它覆盖的其中一列,让这一列选的个数加一,这时我们就可以开始dp了,我们记录\(f_{i,j}\)表示当前到第i行,前\(i-1\)行(每个区间我们假设是在它覆盖的第一列产生的)的区间,有一些用了, 还剩下\(j\)个没有用,对于每一行,这一列有可能产生一个新的区间,也有可能不会(窗口不会滑动之后的每一列虽然可以选,但只能用之前剩下的区间) , 对于每一列, 它的转移只有选几个而已, 每次我选一个数,我一定用的是当前剩下的区间里最左边的区间,因为如果最左边区间能覆盖的点,右边的区间一定也能覆盖, 左边更劣,先把劣区间用了肯定更优。

这个dp能成立的关键在于, 每次用的一定是最左边的区间, 如果之前剩下了\(x\)个区间, 那也一定是当前一格之前最右边的\(x\)个区间。这样我们就不需要记录剩下区间的位置,从而计算哪些区间还能用,而那些区间已经超出范围了, 具体的判断就不多讲了,可看代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#define ll long long 
using namespace std;

int read(){
	int num=0, flag=1; char c=getchar();
	while(!isdigit(c) && c!='-') c=getchar();
	if(c == '-') flag=-1, c=getchar();
	while(isdigit(c)) num=num*10+c-'0', c=getchar();
	return num*flag;
}

const int N = 20;
const int M = 1e5+1000;
int n, m, k;
int a[M][N];
int sum[M][N];
ll f[M][N];
	
int main(){
	n=read(), m=read(), k=read();
	for(int i=1; i<=n; i++){
		for(int j=1; j<=m; j++){
			a[j][i]=read();
		}
	}
	
	for(int i=1; i<=m; i++){
		sort(a[i]+1, a[i]+1+n, [](int i, int j) {
			return i > j;
		});
		for(int j=1; j<=n; j++) sum[i][j] = sum[i][j-1]+a[i][j];
	}
	
	f[m][0]=0, f[m][1]=sum[m][1];
	if(k == 1) f[m][0] = sum[m][1];
	for(int i=m-1; i>=1; i--){
		for(int j=0; j<=k-1; j++){
			int x = j; 
			if(i <= m-k+1) x++;
			for(int l=0; l<=x; l++){
				f[i][j] = max(f[i][j], sum[i][l] + f[i+1][min(x-l, min(k-1, m-k+1-(i+1-k)))]);
			}
		}
	}
	
	printf("%lld\n", f[1][0]);
	
	return 0;
} 
posted @ 2024-07-31 15:48  ltdJcoder  阅读(1)  评论(0编辑  收藏  举报