区间DP
【引言】:
区间DP,比较板,一般情况下就是直接枚举左端点,之后枚举右端点,在左端点和右端点之间枚举断点,用于更新这一段区间
1.【题目】:P4170 [CQOI2007]涂色
【状态设计】:
由于很明显是区间DP,也就没必要分析具体怎么分析了,状态也就是\(f_{i,j}\)表示讲\(s_i\)到\(s_j\)这个区间染色的最优解;
【状态转移】:
考虑\(f_{i,j}\)这个区间;
如果 \(i==j\)的时候,我们发现,就是需要染色一次即可 ,即为 \(f_{i,i}=1\)初始化的时候搞一下就\(OK\)
如果 \(i!=j\)&&\(s_i==s_j\)也就是说,在上一层染色的时候直接给多染色一格,就可以了,即为 \(f_{i,j}=min(f_{i,j-1} , f_{i+1,j})\)即可
如果 \(i!=j\)&&\(s_i!=s_j\) ,这个时候就像上面所说的,枚举断点,合并,即为 \(f_{i,j} = \min\limits_{l≤k leq} f_{i,k} + f_{k+1,j})\)也就是两段区间都需要各自染色,合并取得最小值
【code】
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
using namespace std;
const int maxn=1e6;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){ if(ch == '-') f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0'; ch=getchar();}
return x*f;
}
int n;
int f[60][60];
char s[maxn];
int main()
{
scanf("%s",s+1);
memset(f,0x3f,sizeof(f));
int n=strlen(s+1);
for(int i=1;i<=n;i++)
{
f[i][i]=1;
}
for(int len=1;len<=n;len++) //区间长度
{
for(int l=1,r=l+len;r<=n;l++,r++)
{
if(s[l]==s[r])
{
f[l][r] = min(f[l-1][r],f[l][r-1]);
}
else
{
for(int k=l;k<=r;k++)
{
f[l][r] = min(f[l][r],f[l][k] + f[k+1][r]);
}
}
}
}
printf("%d",f[1][n]);
return 0;
}
【题目】P1880 [NOI1995]石子合并
【引言】:
MD,老和我作对,刚打完码,然后发现圆形操场,然后发现,这个和能量项链十分的相似,同时,写完这个博客就不写能量项链了
【状态设计】:
\(f_{i,j}\)还是表示合并从\(i\)到\(j\)这个区间的极值(最大值和最小值分别设一个就好)。
【状态转移】:
以最大值为例,\(f_{i,j}=\max\limits _{l≤k\leq r} f_{l,k} + f_{k+1,r} + sum_{r} - sum_{l-1}\)
因为合并的时候需要加上前面的值,直接前缀和搞一下就好,最小值也是一样的,本来这个题是要用四边形不等式,但是由于是个板子题,正常区间\(DP\)也可以做,还有注意的是,形成的是一个环,那既然是环,这里就有个小技巧,
就是\(sum_{n+i} = sum_{i}\)这个就说绕了一圈,绕回来了,手动模拟一下也能出来这个结果,推荐手动模拟一下(\(yy\)一下也可,毕竟\(so,easy\))
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#define int long long
using namespace std;
const int maxn=500;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){ if(ch == '-') f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0'; ch=getchar();}
return x*f;
}
int n;
int a[maxn];
int sum[maxn];
int f1[maxn][maxn];//最大值
int f2[maxn][maxn];//最小值
int ans1,ans2;
signed main()
{
n=read();
for(int i=1;i<=n;i++)
{
sum[i] = read();
sum[n+i] = sum[i];
}
for(int i=1;i<n+n;i++)
{
sum[i] += sum[i-1];
//printf("%d\n",sum[i]);
}
for(int i=2;i<=n;i++)
{
for(int l=1;l + i -1 < n+n; l++)
{
int r= l + i -1;
f2[l][r] = 999999999;
f1[l][r] = -999999999;
for(int k=l;k<r;k++)
{
f1[l][r] = max(f1[l][k] + f1[k+1][r] + (sum[r] - sum[l-1]), f1[l][r]);
//printf("%d\n",f1[l][r]);
f2[l][r] = min(f2[l][k] + f2[k+1][r] + (sum[r] - sum[l-1]), f2[l][r]);
//printf("%d\n",f2[l][r]);
}
}
}
ans2 = 999999999; // 0x3f 就真给我搞个 63了
for(int i=1;i<=n;i++)
{
ans1 = max(ans1,f1[i][n+i-1]);//把i写成n,WA了半年
//printf("%d\n",ans2);
ans2 = min(ans2,f2[i][n+i-1]);
}
printf("%d\n",ans2);
printf("%d",ans1);
return 0;
}
3.P433大师
很明显,区间DP,求公差数列个数
很明显,区间DP,求公差数列个数
状态设计: \(f_{i,k}\)表示以\(i\)结尾,公差为\(k\)的公差数列个数,反正我当时是没想出来
状态转移:\(f_{i,k}=f_{j,k}+f_{i,k}+1\)其中 \(1\le j \leq i-1\),
对于一个区间 \([l,r]\)中有等差数列,设公差为\(k\),那么其公差数列中任意挑选一个数设为\(x\),那么表示为\(f_{x,k}\),在这个区间中又有一个数\(y\),如果 \(y-x=k\),自然\(y\)也是属于等差数列中的,但是我们总不能在计算吧,所以我们就DP,也算递推 可得 \(f_{y,k}=f_{y,k}+f_{x,k}+1\),两个数也算一个等差数列 所以加个1;
【code】
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define int long long
using namespace std;
const int maxn=20008;
const int mod=998244353;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int num[maxn];
int f[1003][2*maxn];
int n;
int Max=-1;
int ans;
signed main()
{
n=read();
for(int i=1;i<=n;i++)
{
num[i]=read();
Max=max(Max,abs(num[i]));
}
for(int r=2;r<=n;r++)
{
for(int l=1;l<r;l++)
{
f[r][ num[r]-num[l]+Max ] = (f[r][ num[r]-num[l]+Max ]+f[l][ num[r]-num[l]+Max ]+1)%mod;
ans=(ans+f[l][num[r]-num[l]+Max]+1)%mod;
}
}
ans+=n;
cout<<ans%mod<<endl;
return 0;
}