首先,列出O(n^3)的转移方程
dp[i][j]=max(dp[k][j-1]+f(k+1,i)),f(k+1,i)表示区间[k+1,i]的或和。
因为是或运算,所以把一段分成两段,两段的价值一定大于等于一段的价值,所以dp满足
dp[i][j]>=dp[i][j-1],也就是dp[i][j]>=dp[k][j-1]+f(k+1,i)。
又因为或运算只会增大不会减少,所以也有dp[i][j]>=dp[i-1][j],就相当于在dp[i-1][j]的末尾区间在或上一个a[i],所以结论显然。
于是可以证明决策单调。
我们假设dp[i][j]的决策点是k,那么对于dp[s][j](s>i),dp[s][j]的决策点是h,都有h>=k。
证明很简单: 我们假设dp[s][j]用k前面的决策点进行优化,显然不会比用k来进行优化更优,因为dp[s][j]>=dp[i][j],而dp[i][j]已经证明了最优决策点是k,所以dp[s][j]的用k来优化会优。
而对于大于k的,我们有dp[h][j-1]>=dp[k]j-1,由于dp[h][j-1]是逐渐变大的,而f(h+1,s)<=f(k+1,s),当
dp[h][i-1]逐渐增大而f(h+1,s)不变时,dp[s][i]就会更大,所以大于等于k的区间都有可能使dp[s][j]的决策点。
我们用分治优化
设dp[l,r]表示dp[l][j],dp[l+1][j]…dp[r-1][j],dp[r][j]
假设dp[l,r]的最优决策点在区间[L,R]内,设mid为区间l,r的中点,dp[mid][j]的最优决策点是k,由于决策单调,有:
dp[l,mid-1]的最优决策点一定在[L,k]内
dp[mid+1,r]的最优决策点一定在[k+1,R]内
因此对dp进行分治,时间复杂度O(nk*log2(n))
#include
using namespace std;
typedef long long ll;
const int N=5005;
ll dp[N][N];
int a[N],f[N][N];
int cal(int k,int x,int lb,int rb)
{
int ans=lb;
dp[x][k]=-1;
for(int i=lb;i<=min(rb,x-1);i++)
{
ll s=dp[i][k-1]+f[i+1][x];
if(s>dp[x][k])
{
dp[x][k]=s;
ans=i;
}
}
return ans;
}
void solve(int k,int l,int r,int lb,int rb)
{
if(l>r) return;
if(l==r){cal(k,l,lb,rb);return;}
int m=l+r>>1;
int ans=cal(k,m,lb,rb);
solve(k,l,m-1,lb,ans);
solve(k,m+1,r,ans,rb);
}
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t;
cin>>t;
while(t--)
{
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++)
{
f[i][i]=a[i];
for(int j=i+1;j<=n;j++)
f[i][j]=f[i][j-1]|a[j];
}
for(int i=1;i<=n;i++)
dp[i][1]=f[1][i];
for(int i=2;i<=k;i++)
solve(i,i,n-k+i,i-1,n-k+i-1);
cout<