最大得分 题解(线段树维护dp)
题目链接
题目思路
大部分人使用的是三维dp,只有我这个fw用的阴间线段树维护dp
首先很容易想到一个\(dp\)
设\(dp[i][j]\)表示前\(i\)个元素分为\(j\)个段的最大值
转移方程就是\(dp[i][j]=\max(dp[x][j-1]+gcd(a[x+1],a[x+2]...a[i])) (1\le x \le i-1)\)
答案就是\(dp[n][k]\) 但是这样的复杂度很明显就是\(O(n^2k)\) 妥妥的\(TLE\)
但是你可以将\(gcd(a[x+1],a[x+2]...a[i])\)这些元素分类,最多只有100种,因为\(a[i]\le 100\)
而且你会发现若是固定右端点,枚举左端点的连续那么对于相同\(gcd(a[x+1],a[x+2]...a[i])\)
所对应的位置一定是连续的
\(vec[i][j][0]\)表示以\(a[i]\)为右端点,最左的\(pos\)使得\(\gcd(a[pos],a[pos+1],...a[i])=j\)
\(vec[i][j][1]\)表示以\(a[i]\)为右端点,最右的\(pos\)使得\(\gcd(a[pos],a[pos+1],...a[i])=j\)
然后用线段树维护这个区间的值即可,细节有点多
代码
#include<bits/stdc++.h>
#define debug printf("\n I am here\n");
#define fi first
#define se second
typedef long long ll;
const int maxn=1e4+5,inf=0x3f3f3f3f,mod=1e9+7;
using namespace std;
int n,k;
int a[maxn];
int cal[105][105];
vector<int> vec[maxn][105];
int ma[maxn],mi[maxn];
int tree[maxn<<2][55];
int dp[maxn][55];
int pre[maxn];
void update(int node,int pos,int l,int r,int val,int id){
if(l==r){
tree[node][id]=val;
return ;
}
int mid=(l+r)/2;
if(mid>=pos) update(node<<1,pos,l,mid,val,id);
else update(node<<1|1,pos,mid+1,r,val,id);
tree[node][id]=max(tree[node<<1][id],tree[node<<1|1][id]);
}
int query(int node,int L,int R,int l,int r,int id){
if(L>R) return -inf;
if(L<=l&&r<=R){
return tree[node][id];
}
int mid=(l+r)/2,ma=-inf;
if(mid>=L) ma=max(ma,query(node<<1,L,R,l,mid,id));
if(mid<R) ma=max(ma,query(node<<1|1,L,R,mid+1,r,id));
return ma;
}
void build(int node,int l,int r){
for(int i=1;i<=40;i++){
tree[node][i]=-inf;
}
if(l==r){
return ;
}
int mid=(l+r)/2;
build(node<<1,l,mid);
build(node<<1|1,mid+1,r);
}
int main(){
scanf("%d%d",&n,&k);
build(1,1,n);
for(int i=1;i<=100;i++){
for(int j=1;j<=100;j++){
cal[i][j]=__gcd(i,j);
// cal[i][j]表示i和j的gcd
}
}
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=100;j++){
ma[j]=-inf;
mi[j]=inf;
}
int temp=a[i];
for(int j=i;j>=1;j--){
temp=cal[temp][a[j]];
mi[temp]=min(mi[temp],j);
ma[temp]=max(ma[temp],j);
}
for(int j=1;j<=100;j++){
if(mi[j]==inf) continue;
vec[i][j].push_back(mi[j]);
vec[i][j].push_back(ma[j]);
}
}
for(int i=0;i<=n;i++){
for(int j=0;j<=50;j++){
dp[i][j]=-inf;
}
}
for(int i=1;i<=n;i++){
pre[i]=__gcd(pre[i-1],a[i]);
}
// dp[i][j] 表示前i个分为j个段
for(int i=1;i<=n;i++){
for(int j=1;j<=min(i,k);j++){
if(j==1){
dp[i][j]=pre[i];
}else{
for(int u=1;u<=100;u++){
if(vec[i][u].size()==0) continue;
int x=query(1,max(vec[i][u][0]-1,1),vec[i][u][1]-1,1,n,j-1);
dp[i][j]=max(dp[i][j],x+u);
}
}
update(1,i,1,n,dp[i][j],j);
}
}
printf("%d\n",dp[n][k]);
return 0;
}
不摆烂了,写题