1. 程式人生 > >「NOIP2018模擬賽」 最優序列

「NOIP2018模擬賽」 最優序列

題目描述

給出一個長度為n(n<=1000) 的正整數序列,求一個子序列,使得原序列中任意長度為 m的子串中被選出的元素不超過k(k<=m<=10)個,並且選出的元素之和最大。

分析

看到範圍,應該是能想到狀壓Dp的,然而在考場上只打出了一個10分的暴力。當然,可以用費用流做,不過網路流應該不是NOIP的考點吧。

定義狀態f[i][j]為前i個數,以i為結尾往前m個數的狀態為j所得的最大值。容易知道對於第p個數,只有選與不選兩種情況,以此作為轉移的要素,可以先將這個狀態的最高位抹去,在左移1位,使之形成i-1的狀態,進行更新。對於每個狀態的j是固定的,所以可以先預處理出合法的狀態,即狀態中1的個數不超過k的狀態。本題還可用滾動陣列來優化空間,不過對於n<=1000的資料也沒必要了。

程式碼

#include <iostream>
#include <cstring>
#include <cstdio>
#define lowbit(x) ((x)&-(x))
using namespace std;
int n,m,k,mx;
int a[1005];
bool st[1025];
int f[2][1025];
int main() {
	scanf("%d%d%d",&n,&m,&k);
	for (int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	mx=1<<m;
	for (int i=0;i<mx;i++) {//預處理合法狀態 
		int ret=0;
		for (int j=i;j;j-=lowbit(j)) {//lowbit為二進位制下最低位的1 
			ret++;
		}
		if (ret<=k) st[i]=1;
	}
	f[0][0]=0;
	int now=0;
	for (int i=1;i<=n;i++) {
		now^=1;
		memset(f[now],-0x3f,sizeof f[now]);
		for (int j=0;j<mx;j++) {
			if (st[j]&&f[now^1][j]>=0) {
				int s0=(j&((1<<m-1)-1))<<1,s1=(j&((1<<m-1)-1))<<1|1;
				//將j的最高位抹去,左移1位,s0為不取的狀態,s1為取的狀態 
				if (st[s0])
				f[now][s0]=max(f[now][s0],f[now^1][j]);//不取第i個 
				if (st[s1])
				f[now][s1]=max(f[now][s1],f[now^1][j]+a[i]);//取第i個 
			}
		}
	}
	int ans=-0x3f3f3f3f;
	for (int i=0;i<mx;i++)
		if (st[i]) ans=max(ans,f[now][i]);//找答案 
	printf("%d",ans);
	return 0;
}