区间DP 学习笔记
xujindong_ · · 个人记录
概述
区间 DP 是 DP 状态是一段区间的信息的 DP。这种问题,可能子状态是天然的一段区间。更多的时候问题涉及合并或分裂。一个元素由原序列的一段区间的元素合并而成,我们可以考虑当前区间由哪些段合并而成。或者在区间内取一些特殊元素,分成若干区间,这些区间是比较独立的子问题。一种特殊情况是笛卡尔树式的 DP,取区间内最大/最小/最早/最晚的元素,分成两边独立的区间。
状态通常是
例题
P9523
显然我们只会打出
#include<bits/stdc++.h>
using namespace std;
int n,l[2505][2505],nxt[2505][2505];
long long f[2505][2505],a,b,c;
char s[2505];
int main(){
memset(f,0x3f,sizeof(f)),cin>>n>>s+1>>a>>b>>c;
for(int i=n;i>=1;i--)for(int j=n;j>=i;j--)l[i][j]=s[i]==s[j]?l[i+1][j+1]+1:0;
for(int i=1;i<=n;i++)for(int j=i+1,k=i;j<=n;j++)while(k<min(j,i+l[i][j]))nxt[i][k]=j+k-i,k++;
for(int len=1;len<=n;len++){
for(int l=1,r=len;r<=n;l++,r++){
f[l][r]=l==r?a:min(f[l][r],min(f[l+1][r],f[l][r-1])+a);
for(int i=nxt[l][r],j=2;i;i=nxt[i-len+1][i],j++)f[l][i]=min(f[l][i],f[l][r]+b+j*c+(i-l+1-j*len)*a);
}
}
return cout<<f[1][n]<<'\n',0;
}
AT_arc108_e
核心观察是对于一个区间
#include<bits/stdc++.h>
using namespace std;
const int mod=1000000007;
int n,a[2005],inv[2005],f[2005][2005];
template<typename T,int maxn>struct BIT{
int tr[maxn];
void add(int x,int k){
for(;x<=n;x+=x&-x)tr[x]=(tr[x]+k)%mod;
}
int query(int x,int ans=0){
for(;x;x-=x&-x)ans=(ans+tr[x])%mod;
return ans;
}
};
BIT<int,2005>g[2005],fl[2005],fr[2005];
int main(){
inv[0]=inv[1]=1,cin>>n,n+=2;
for(int i=2;i<=n;i++)inv[i]=1ll*inv[mod%i]*(mod-mod/i)%mod;
for(int i=2;i<n;i++)cin>>a[i],a[i]++;
a[1]=1,a[n]=n;
for(int i=1;i<n;i++)g[i].add(a[i+1],1);
for(int len=3;len<=n+2;len++){
for(int l=1,r=len;r<=n;l++,r++){
int cnt=g[l].query(a[r])-g[l].query(a[l]-1);
if(a[l]<a[r]&&cnt)f[l][r]=(1ll*((fl[l].query(a[r])-fl[l].query(a[l]-1)+fr[r].query(a[r])-fr[r].query(a[l]-1))%mod+mod)*inv[cnt]+1)%mod;
g[l].add(a[r],1),fl[l].add(a[r],f[l][r]),fr[r].add(a[l],f[l][r]);
}
}
return cout<<f[1][n]<<'\n',0;
}
P4563
对于区间
证明:设
考虑区间 DP,枚举右端点,左端点不断向左扩展。在扩展的过程中可以直接维护出右端点能看到的点分成的段。设相邻两分界点为
#include<bits/stdc++.h>
using namespace std;
int n,h[5005],f[5005][5005],ans;
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>h[i];
for(int i=1;i<=n;i++){
f[i][i]=1,ans^=1;
int sum=1,l=0;
for(int j=i-1;j>=1;j--){
if(!l||1ll*(h[i]-h[j])*(i-l)<1ll*(h[i]-h[l])*(i-j))sum+=l?min(f[j+1][l],f[j+1][l-1]):0,l=j;
f[j][i]=min(f[j][l],f[j][l-1])+sum,ans^=f[j][i];
}
}
return cout<<ans<<'\n',0;
}
QOJ8089
首先有个区间 DP,因为第一次之后分割点总是
注意到直接二分答案不超过
对于每一层
查询时,枚举第一次问
#include<bits/stdc++.h>
using namespace std;
int t,l,r,tr[100005][32],f[47][100005],g[47][100005],w[32][161056];
vector<int>s[6];
vector<pair<int,int> >e[100005];
int main(){
memset(g,0x3f,sizeof(g));
for(int i=0;i<32;i++)s[__builtin_popcount(i)].push_back(i);
for(int i=0;i<=100000;i++)for(int j=0;j<32;j++)for(int k=0,p=1,now=i;k<5;k++,p*=11,now/=10)tr[i][j]+=p*(j>>k&1?10:now%10);
for(int i=1;i<=99999;i++)f[0][i]=g[0][i]=i;
for(int x=0;x<=42;x++)g[x][0]=1,f[x][100000]=99999;
for(int x=1;x<=42;x++){
for(int i=1;i<=99999;i++)e[i].clear();
for(int i=0;i<32;i++)for(int j=0;j<=161051;j++)w[i][j]=0;
for(int i=1;i<=99999;i++)for(int d=1;d<=min(6,x);d++)e[g[x-d][i-1]].push_back(make_pair(i,d));
for(int i=1;i<=99999;i++){
for(int j=0;j<e[i].size();j++){
int p=e[i][j].first,d=e[i][j].second;
for(int k=0;k<s[d-1].size();k++){
int mask=s[d-1][k];
w[mask][tr[p][mask]]=max(w[mask][tr[p][mask]],f[x-d][p+1]);
}
}
for(int mask=0;mask<32;mask++)f[x][i]=max(f[x][i],w[mask][tr[i-1][mask]]);
}
for(int i=1;i<=99999;i++)e[i].clear();
for(int i=0;i<32;i++)for(int j=0;j<=161051;j++)w[i][j]=100000;
for(int i=1;i<=99999;i++)for(int d=1;d<=min(6,x);d++)e[f[x-d][i+1]].push_back(make_pair(i,d));
for(int i=99999;i>=1;i--){
for(int j=0;j<e[i].size();j++){
int p=e[i][j].first,d=e[i][j].second;
for(int k=0;k<s[d-1].size();k++){
int mask=s[d-1][k];
w[mask][tr[p][mask]]=min(w[mask][tr[p][mask]],g[x-d][p-1]);
}
}
for(int mask=0;mask<32;mask++)g[x][i]=min(g[x][i],w[mask][tr[i+1][mask]]);
}
}
cin>>t;
while(t--){
cin>>l>>r;
int ans=0x3f3f3f3f;
for(int i=l;i<=r;i++){
int l1=0,r1=42,l2=0,r2=42;
while(l1<r1){
int mid=(l1+r1)>>1;
if(g[mid][i-1]<=l)r1=mid;
else l1=mid+1;
}
while(l2<r2){
int mid=(l2+r2)>>1;
if(f[mid][i+1]>=r)r2=mid;
else l2=mid+1;
}
int now=max(l1,l2);
for(int j=i;j;j/=10)if(j%10)now++;
ans=min(ans,now+1);
}
cout<<ans<<'\n';
}
return 0;
}
CF2129D
考虑用笛卡尔树式的区间 DP 刻画这个加分,好处是这样刻画了变成黑格的先后顺序。且一个区间左右两端变成了黑色,里面的就只能加到内部。设
可以观察到,分数不超过
#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
int t,n,s[105],fac[105],vac[105],f[105][105][15][15];
int C(int n,int m){
return n<0||m<0||n<m?0:1ll*fac[n]*vac[m]%mod*vac[n-m]%mod;
}
int main(){
fac[0]=vac[0]=vac[1]=1;
for(int i=2;i<=100;i++)vac[i]=1ll*vac[mod%i]*(mod-mod/i)%mod;
for(int i=1;i<=100;i++)fac[i]=1ll*fac[i-1]*i%mod,vac[i]=1ll*vac[i]*vac[i-1]%mod;
cin>>t;
while(t--){
cin>>n;
int sum=0,lim=2*__lg(n);
bool flag=0;
for(int i=1;i<=n;i++)cin>>s[i],flag|=s[i]>lim,sum+=s[i]==-1?0:s[i];
if(flag||sum>n){
puts("0");
continue;
}
s[0]=s[n+1]=-1;
for(int i=1;i<=n+1;i++)f[i][i-1][0][0]=1;
for(int len=1;len<=n;len++){
for(int l=1,r=len;r<=n;l++,r++){
for(int a=0;a<=(s[l-1]==-1?0:s[l-1]);a++){
for(int b=0;b<=(s[r+1]==-1?0:s[r+1]);b++){
for(int i=l;i<=r;i++){
bool fl,fr;
if(l!=1&&r!=n){
if(i-l<=r-i)fl=1,fr=0;
else fl=0,fr=1;
}
else fl=l!=1,fr=r!=n;
int na=s[l-1]==-1?a:a-fl,nb=s[r+1]==-1?b:b-fr;
if(na<0||nb<0)continue;
if(s[i]==-1)f[l][r][a][b]=(f[l][r][a][b]+1ll*f[l][i-1][na][0]*f[i+1][r][0][nb]%mod*C(r-l,i-l))%mod;
else for(int x=0;x<=s[i];x++)f[l][r][a][b]=(f[l][r][a][b]+1ll*f[l][i-1][na][x]*f[i+1][r][s[i]-x][nb]%mod*C(r-l,i-l))%mod;
}
}
}
}
}
cout<<f[1][n][0][0]<<'\n';
for(int i=1;i<=n;i++)for(int j=i;j<=n;j++)for(int a=0;a<=lim;a++)for(int b=0;b<=lim;b++)f[i][j][a][b]=0;
}
return 0;
}