「NOIP2018模擬賽」 最優序列
阿新 • • 發佈:2018-12-11
題目描述
給出一個長度為n(n<=1000) 的正整數序列,求一個子序列,使得原序列中任意長度為 m的子串中被選出的元素不超過k(k<=m<=10)個,並且選出的元素之和最大。
分析
看到範圍,應該是能想到狀壓Dp的,然而在考場上只打出了一個10分的暴力。當然,可以用費用流做,不過網路流應該不是NOIP的考點吧。
定義狀態為前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; }