nowcoder 2019noip暑假7天营Day4 T2卖羊驼
\(V\)为最大权值,\(K\)为总段数,\(n\)为羊驼总数,\(p[i]\)表示第 i 只羊驼的权值,\(dp[i][k]\)表示在 i 及 i 之前将序列分解为k组的最优解,\(lp[j]\)表示在 i 往前第一个二进制第 j 位为1的羊驼 , \(or\_val[j]\) 为从 \(i\) 到 \(lp[j]\) 的或和
根据如上定义,我们可以得到一个动态转移方程
\[dp[i][k]=max(dp[i][k],dp[lp[j]][k-1]+or\_val[j])
\]
其中 j 从 0 到 \(\lceil log_2(V)\rceil\),时间复杂度为\(O(nKlog_2(V))\)
实际上,我们最初的想法或许是这样的(其中i,j,k与上无关)
\(dp[i][j] = max(dp[i][j],dp[k-1][j-1]+or\_sum(k,i))\)
其中 k 为枚举 i 往前的任意一个节点
可是再望时间复杂度\(O(n^2K)\),受不起
我们再想(这是一个绝妙的点),或和的本质是对于每一个二进制位上来说,若其中一个值在这一位上为1,则或和在这一位上也为1。而对于一个节点 i 来说,若有两个节点 a , b \((a<b)\),它们的值均在某一位上为1,那么选择 b 作为 新阶段的开始,会比 a 更优。
而对于 第二个转移方程中的 k 的枚举,本质上是对二进制位的枚举,以求出最优答案。
因而,我们会有如下代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int const MAXN=5010,MAXK=1010;
int n,K;
int p[MAXN],dp[MAXN][MAXK],lp[MAXN],or_val[MAXN];
signed main(){
scanf("%lld%lld",&n,&K);
for(int i=1;i<=n;i++)scanf("%lld",&p[i]);
for(int i=1;i<=n;i++){
for(int j=0;j<=24;j++){//24
if((p[i]>>j)&1){
or_val[j]=p[i];
lp[j]=i;
}
or_val[j]|=p[i];
}
for(int k=1;k<=K && k<=i;k++){
for(int j=0;j<=24;j++){
if(lp[j]>=k)dp[i][k]=max(dp[i][k],dp[lp[j]-1][k-1]+or_val[j]);
}
}
}
printf("%lld\n",dp[n][K]);
return 0;
}
本代码对\(or\_val,lp\)采取的是在 dp 中更新
而还有另外一种玩法,就是在 dp 前就预处理好,这时对于 \(or\_val[i][j]\) 的处理便可以用ST表我绝不是因为我不会才没有写