题目见蓝皮书上的线性DP
No1,Mr. Youngs Picture Permutations
这一题一看数据:30也就是说最多有150个,那我们就可以大胆假设dp方程,就很容易推出如下式子
设f[a1][a2][a3][a4][a5]表示第一行a1个人,第二行a2个人……第五行a5个人
然后我们可以从最小的开始排我们只要保证每一次排的位置的下面和右面没有就行了
- f[a1+1][a2][a3][a4][a5]+=f[a1][a2][a3][a4][a5](a1<=m1)
- f[a1][a2+1][a3][a4][a5]+=f[a1][a2][a3][a4][a5](a2<=m2 a2<a1)
- f[a1][a2][a3+1][a4][a5]+=f[a1][a2][a3][a4][a5](a3<=m3 a3<a2)
- f[a1][a2][a3][a4+1][a5]+=f[a1][a2][a3][a4][a5](a4<=m4 a4<a3)
- f[a1][a2][a3][a4][a5+1]+=f[a1][a2][a3][a4][a5](a5<=m5 a5<a4)
这就是一个完整的转态转移,代码如下:
#include<bits/stdc++.h>
using namespace std;
int m[6],K;
int read()
{
int num=0;bool flag=1;
char c=getchar();
for(;c<'0'||c>'9';c=getchar())
if(c=='-')flag=0;
for(;c>='0'&&c<='9';c=getchar())
num=(num<<1)+(num<<3)+c-'0';
return flag?num:-num;
}
int main()
{
freopen("i.in","r",stdin);
freopen("i.out","w",stdout);
K=read();
while(K!=0)
{
memset(m,0,sizeof(m));
for(int i=1;i<=K;i++)
m[i]=read();
int f[m[1]+1][m[2]+1][m[3]+1][m[4]+1][m[5]+1];
memset(f,0,sizeof(f));
f[0][0][0][0][0]=1;
for(int i=0;i<=m[1];i++)
for(int j=0;j<=m[2];j++)
for(int k=0;k<=m[3];k++)
for(int l=0;l<=m[4];l++)
for(int o=0;o<=m[5];o++)
{
if(i<m[1]) f[i+1][j][k][l][o]+=f[i][j][k][l][o];
if(j<m[2]&&i>j)f[i][j+1][k][l][o]+=f[i][j][k][l][o];
if(k<m[3]&&j>k)f[i][j][k+1][l][o]+=f[i][j][k][l][o];
if(l<m[4]&&k>l)f[i][j][k][l+1][o]+=f[i][j][k][l][o];
if(o<m[5]&&l>o)f[i][j][k][l][o+1]+=f[i][j][k][l][o];
}
printf("%lld\n",f[m[1]][m[2]][m[3]][m[4]][m[5]]);
K=read();
}
return 0;
}
NO2,LCIS
这是一道LCS和LIS的综合题目
只要这两个想的透彻,就可以知道一个状态转移
f[i][j]表示A1~Ai和B1~Bi构成以Bj结尾的最长LICS的长度
则当Ai=Bj时则可知f[i][j]=max{f[i-1][k]}+1(0<=k<j Bk<Ai)
否则f[i][j]=f[i-1][j]
可是这个是三重循环,很显然这会超时,所以我们可以想怎么把k这一重循环给省略掉
可以发现0<=k<j,那么就知道了,我们在枚举j的时候,就已经知道了k的范围了
那就可以用一个变量来记录前面的最大值就可以了,在转移上就只有用O1
代码如下:
#include<bits/stdc++.h>
using namespace std;
int read()
{
int num=0;bool flag=1;
char c=getchar();
for(;c<'0'||c>'9';c=getchar())
if(c=='-')flag=0;
for(;c>='0'&&c<='9';c=getchar())
num=(num<<1)+(num<<3)+c-'0';
return flag?num:-num;
}
const int N=3010;
int n,a[N],b[N];
long long f[N];
int main()
{
freopen("test.in","r",stdin);
freopen("test.out","w",stdout);
n=read();
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=n;i++)b[i]=read();
for(int i=1;i<=n;i++)
{
long long sum=0;
for(int j=1;j<=n;j++)
{
if(a[i]>b[j])sum=max(sum,f[j]);
if(a[i]==b[j])f[j]=sum+1;
}
}
long long maxx=0;
for(int i=1;i<=n;i++)
maxx=max(maxx,f[i]);
cout<<maxx;
return 0;
}
NO3,Making the Grade
这道题可以知道如下方程:
f[i][j]表示完成了i个数,Bi=j是时的最小值
i是2000,没问题,主要是枚举j,就要引出下列定理:
在满足S最小化下,一定存在一种构造B的方案,使每一个数值都在A中出现过。
证明:
对于N=1事显然成立。
那么就设N=k-1成立,证明N=k时也成立
当N=k时若B(k-1)<=Ak,则让Bk=Ak那么满足单调性且命题成立
否则,另Bk=B(k-1)命题仍然成立
要么存在一个j,让Bj~Bk的值为同一个:v
设Aj~Ak的中位数为mid,如果mid>=b(j-1)则v=mid否则v=B(j-1) 2种情况均在A中出现过
证毕
所以我们可以把A离散化,这样就可以做了
代码如下
#include<bits/stdc++.h>
using namespace std;
int read()
{
int num=0;bool flag=1;
char c=getchar();
for(;c<'0'||c>'9';c=getchar())
if(c=='-')flag=0;
for(;c>='0'&&c<='9';c=getchar())
num=(num<<1)+(num<<3)+c-'0';
return flag?num:-num;
}
const int N=2001;
long long f[N][N];
int n,b[N],a[N];
int main()
{
freopen("grading..in","r",stdin);
freopen("grading..out","w",stdout);
n=read();
for(int i=1;i<=n;i++)
{
a[i]=read();
b[i]=a[i];
}
sort(b+1,b+n+1);
memset(f,10,sizeof(f));
f[0][0]=0;
for(int i=1;i<=n;i++)
{
long long maxx=f[i-1][0];
for(int j=1;j<=n;j++)
{
maxx=min(maxx,f[i-1][j]);
f[i][j]=maxx+abs(a[i]-b[j]);
}
}
long long minn=INT_MAX;
for(int i=1;i<=n;i++)
minn=min(minn,f[n][i]);
reverse(b+1,b+n+1);
memset(f,10,sizeof(f));
f[0][0]=0;
for(int i=1;i<=n;i++)
{
long long maxx=f[i-1][0];
for(int j=1;j<=n;j++)
{
maxx=min(maxx,f[i-1][j]);
f[i][j]=maxx+abs(a[i]-b[j]);
}
}
for(int i=1;i<=n;i++)
minn=min(minn,f[n][i]);
cout<<minn;
return 0;
}
No4,Mobile Service
很容易想到以下方程:
f[i][x][y][z]表示完成了前i个任务,服务员123分别在xyz的位置转移方程也很容易想
但是可以想到,当i完成时,可定有一个人在pi这么一个位置,可以减一维,f[i][x][y]表示有两个人在xy的位置,一个在pi时的方案
- f[i+1][x][y]=min(f[i][x][y]+c(go[i],go[i+1]))
- f[i+1][go[i]][y]=min(f[i+1][x][y]+c(x,go[i+1]))
- f[i+1][x][go[i]]=min(f[i+1][x][y]+c(y,go[i+1]))
代码如下
#include<bits/stdc++.h>
using namespace std;
int read()
{
int num=0;bool flag=1;
char c=getchar();
for(;c<'0'||c>'9';c=getchar())
if(c=='-')flag=0;
for(;c>='0'&&c<='9';c=getchar())
num=(num<<1)+(num<<3)+c-'0';
return flag?num:-num;
}
const int N=1010;
const int L=210;
int f[N][L][L],n,l;
int a[L][L],b[N];
int main()
{
freopen("test.in","r",stdin);
freopen("test.out","w",stdout);
b[0]=3;
n=read();l=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
a[i][j]=read();
for(int i=1;i<=l;i++)b[i]=read();
memset(f,10,sizeof(f));f[0][1][2]=0;
for(int i=0;i<l;i++)
for(int x=1;x<=n;x++)
for(int y=1;y<=n;y++)
{
if(x==y||x==b[i]||y==b[i])continue;
f[i+1][x][y]=min(f[i+1][x][y],f[i][x][y]+a[b[i]][b[i+1]]);
f[i+1][b[i]][y]=min(f[i+1][b[i]][y],f[i][x][y]+a[x][b[i+1]]);
f[i+1][x][b[i]]=min(f[i+1][x][b[i]],f[i][x][y]+a[y][b[i+1]]);
}
int minn=INT_MAX;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
minn=min(minn,f[l][i][j]);
printf("%d",minn);
return 0;
}
NO5.传纸条
在这,我们可以用f[x1][y1][x2]表示一条线1到x1,y1,另一条到x2,y2(y2是可以计算,因为只有两条的长度是相同的,所以我们可得y2=x1+y1-x2),我们用贪心即可知两条线不可能相交,所以我们可以用这一个性质,就可以做了。
#include<bits/stdc++.h>
using namespace std;
int read()
{
int num=0;bool flag=1;
char c=getchar();
for(;c<'0'||c>'9';c=getchar())
if(c=='-')flag=0;
for(;c>='0'&&c<='9';c=getchar())
num=(num<<1)+(num<<3)+c-'0';
return flag?num:-num;
}
int MAX(int a,int b,int c,int d)
{return max(max(a,b),max(c,d));}
const int N=51;
int n,m,a[N][N],f[N][N][N][N];
int main()
{
freopen("test.in","r",stdin);
freopen("test.out","w",stdout);
n=read();m=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
a[i][j]=read();
for(int x1=1;x1<=n;x1++)
for(int y1=1;y1<=m;y1++)
for(int x2=1;x2<min(x1+y1,n+1);x2++)
{
int y2=x1+y1-x2;if(y2>m)continue;
f[x1][y1][x2][y2]=MAX(f[x1][y1-1][x2][y2-1],f[x1-1][y1][x2][y2-1],f[x1][y1-1][x2-1][y2],
f[x1-1][y1][x2-1][y2])+a[x1][y1]+a[x2][y2];
if(x1==x2&&y1==y2)f[x1][y1][x2][y2]-=a[x1][y1];
// cout<<x1<<' '<<y1<<' '<<x2<<' '<<y2<<' '<<f[x1][y1][x2][y2]<<endl;
}
cout<<f[n][m][n][m];
return 0;
}
NO.6 I-country
这是一道毒瘤DP题,但是很简单,我们先讲花园。
题目描述
现在有一个矩形花园,大小为n×mn×m,每个格子里有一株植物。
现在你要从中选取一些植物,使得不存在以下情况―― 1.存在A植物与B植物在同一行或者同一列,而且A与B都被选择了,但是他们中间的植物却没有被选上)。
2.存在一个以上的联通块。
3.选择的植物数为0。 请问有多少种合法的选择方案?
输入格式
一行,两个整数,表示n,mn,m。
输出格式
一行,一个整数,表示答案。 mod 1000000007
样例数据
input
3 4
output
571
数据规模与约定
20%的数据 n<=3 m<=3
50% 的数据 n<=20 m<=20
100% 的数据 n<=50 m<=50
时间限制:1s1s
空间限制:256MB
用一个f[i][j][k][a][b](a,b是1,0)表示第i行,从i,j是单调递增或单调递减的方案数
则有了如下代码(方程就在代码中体现)
#include<bits/stdc++.h>
using namespace std;
const int Mod=1000000007;
const int N=60;
long long n,m,f[N][N][N][2][2];
int main()
{
freopen("plant.in","r",stdin);
freopen("plant.out","w",stdout);
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int l=1;l<=m;l++)
for(int r=l;r<=m;r++)
f[i][l][r][0][0]=1;
// f[i][l][r][0][1]=1,
// f[i][l][r][1][0]=1,
// f[i][l][r][1][1]=1;
for(int i=1;i<=n;i++)
for(int l=1;l<=m;l++)
for(int r=l;r<=m;r++)
{
//0,0
for(int j=1;j<=l;j++)
for(int k=r;k<=m;k++)
f[i+1][j][k][0][0]=(f[i][l][r][0][0]+f[i+1][j][k][0][0])%Mod;
//1,1
for(int j=l;j<=r;j++)
{
for(int k=j;k<=r;k++)
f[i+1][j][k][1][1]=(f[i][l][r][1][1]+f[i+1][j][k][1][1])%Mod;
for(int k=j;k<r;k++)
f[i+1][j][k][1][1]=(f[i][l][r][1][0]+f[i+1][j][k][1][1])%Mod;
}
for(int k=r;k>=l;k--)
for(int j=l+1;j<=k;j++)
f[i+1][j][k][1][1]=(f[i][l][r][0][1]+f[i+1][j][k][1][1])%Mod;
for(int j=l+1;j<=r;j++)
for(int k=j;k<r;k++)
f[i+1][j][k][1][1]=(f[i][l][r][0][0]+f[i+1][j][k][1][1])%Mod;
//0,1
for(int j=1;j<=l;j++)
{
for(int k=l;k<=r;k++)
f[i+1][j][k][0][1]=(f[i][l][r][0][1]+f[i+1][j][k][0][1])%Mod;
for(int k=l;k<r;k++)
f[i+1][j][k][0][1]=(f[i][l][r][0][0]+f[i+1][j][k][0][1])%Mod;
}
//1,0
for(int k=m;k>=r;k--)
{
for(int j=l;j<=r;j++)
f[i+1][j][k][1][0]=(f[i][l][r][1][0]+f[i+1][j][k][1][0])%Mod;
for(int j=l+1;j<=r;j++)
f[i+1][j][k][1][0]=(f[i][l][r][0][0]+f[i+1][j][k][1][0])%Mod;
}
}
int ans=0;
for(int i=1;i<=n;i++)
for(int l=1;l<=m;l++)
for(int r=l;r<=m;r++)
ans=(ans+f[i][l][r][0][0]+f[i][l][r][1][0]+f[i][l][r][0][1]+f[i][l][r][1][1])%Mod;
cout<<ans;
return 0;
}
同理,I-country也是一样,我们就可以在加一维来表示这一行的个数,就可以做了个很简单的转移,但是这题还需输出一个方案数,因此我们可以在做的是后用4个数组来记录这一步是从上一步的哪一步记录的就可以做了。
代码:(DP方程在里面)
#include<bits/stdc++.h>
using namespace std;
int read()
{
int num=0;bool flag=1;
char c=getchar();
for(;c<'0'||c>'9';c=getchar())
if(c=='-')flag=0;
for(;c>='0'&&c<='9';c=getchar())
num=(num<<1)+(num<<3)+c-'0';
return flag?num:-num;
}
const int N=20;
int f[N][N*N][N][N][2][2],b[N][N];
int n,m,t,i,j,k,l,r,x,y;
int p,q,now;
char al[N][N*N][N][N][2][2],ar[N][N*N][N][N][2][2];
char bl[N][N*N][N][N][2][2],br[N][N*N][N][N][2][2];
void SUM(int dat,int L,int R,int X,int Y)
{
if(dat<f[i][j][l][r][x][y]) return;
f[i][j][l][r][x][y]=dat;
al[i][j][l][r][x][y]=L,ar[i][j][l][r][x][y]=R;
bl[i][j][l][r][x][y]=X,br[i][j][l][r][x][y]=Y;
}
void print(int i,int j,int l,int r,int x,int y)
{
if(!j) return;
int ii=j-r+l-1;
print(i-1,ii,
al[i][j][l][r][x][y],ar[i][j][l][r][x][y],
bl[i][j][l][r][x][y],br[i][j][l][r][x][y]);
for(j=l;j<=r;j++) printf("%d %d\n",i,j);
}
int main()
{
freopen("input.in","r",stdin);
freopen("output.out","w",stdout);
n=read();m=read();t=read();
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)
{
b[i][j]=read();
b[i][j]=b[i][j-1]+b[i][j];
}
int maxx=0;
for(i=1;i<=n;i++)
for(j=1;j<=t;j++)
for(l=1;l<=m;l++)
for(r=l;r<=m;r++)
{
int now=b[i][r]-b[i][l-1];
if((k=r-l+1)>j)break;
x=y=1;
for(p=l;p<=r;p++)
for(q=p;q<=r;q++)
SUM(f[i-1][j-k][p][q][1][1]+now,p,q,1,1);
x=y=0;
for(p=1;p<=l;p++)
for(q=r;q<=m;q++)
{
SUM(f[i-1][j-k][p][q][0][0]+now,p,q,0,0);
SUM(f[i-1][j-k][p][q][1][0]+now,p,q,1,0);
SUM(f[i-1][j-k][p][q][0][1]+now,p,q,0,1);
SUM(f[i-1][j-k][p][q][1][1]+now,p,q,1,1);
}
x=1,y=0;
for(p=l;p<=r;p++)
for(q=r;q<=m;q++)
{
SUM(f[i-1][j-k][p][q][1][0]+now,p,q,1,0);
SUM(f[i-1][j-k][p][q][1][1]+now,p,q,1,1);
}
x=0,y=1;
for(p=1;p<=l;p++)
for(q=l;q<=r;q++)
{
SUM(f[i-1][j-k][p][q][0][1]+now,p,q,0,1);
SUM(f[i-1][j-k][p][q][1][1]+now,p,q,1,1);
}
}
int L,R,X,Y,Lin;
for(i=1;i<=n;i++)
for(l=1;l<=m;l++)
for(r=l;r<=m;r++)
for(x=0;x<2;x++)
for(y=0;y<2;y++)
if(maxx<f[i][t][l][r][x][y])
{
maxx=f[i][t][l][r][x][y];
Lin=i;L=l;R=r;X=x;Y=y;
}
cout<<"Oil : "<<maxx<<endl;
print(Lin,t,L,R,X,Y);
return 0;
}
NO.7 Cookies
这一题也是还好的。首先,我们贪婪度大的孩子应该得到更多的饼干,因此首先可以把孩子的贪婪度从大到小,孩子得到饼干的数量将是单调递减的设f[i,j]表示前i个孩子一个分配了j块饼干时,怒气值总和的最小值,直观的思考是考虑分配给第i+1个孩子多少饼干,然后进行转移。
转移时有两种情况:
1.当前孩子的贪婪度比下一个孩子大,即:第i个孩子的饼干数比第i+1个孩子多,a[i+1]=i;
2.当前孩子的贪婪度与下一个孩子一样,即:第i个孩子的饼干数与第i+1个孩子一样,那么这时我们还需要知道前i个孩子中有多少个获得饼干数与第i个孩子相同才能求出a[i+1]
无论哪种情况,我们都需要知道第i个孩子获得的饼干数,以及i前面有多少个孩子与i获得的饼干数相同,然而在现有DP状态下,很难高效维护这两个信息
那么我们不妨对状态转移做一个等价交换,
1.若第i个孩子获得的饼干数大于1,则等价于分配j-i个饼干给前i个孩子,也就是说平均每个孩子都少分一块饼干,获得饼干数的相对大小顺序不变,从而怨气和也不变
2.若第i个孩子获得的饼干数为1,枚举i前面有多少个孩子也获得1块饼干
想想为什么能这样,我们在第i个孩子那,用j-i的方式强行使前i个孩子每个人获得的饼干都少了1,那么这样不断-1-1-1-1...下去,肯定会出现到最后只获得了1块的情况,由1状态可知,这样操作下去并不会是结果有任何变化,所以2状态并不会对结果产生影响,这样我们也就能得到状态转移方程
i
f[i,j] = min{f[i,j-i], min{f[k,j-(i-k)] + k * Σ g[p]}(0<=k<i)]}
p=k+1
i
对min中嵌套的min,我们先挨个枚举k,f[k,j-(i-1)]表示到第k个孩子,共分了j-(i-k)块饼干。k*Σ g[p]则表示假设从第k
p=k+1
个孩子向后获得的饼干数都相同,总的怨气和,这样一遍后,我们得到加入i之前有和i获得饼干相等的情况,所能得到的最小怨气和,然后再与i之前没有孩子与i获得饼干数相等的情况所需的怨气和,就能得到第i个孩子得到第j个饼干并且怨气和最小结果
代码:
#include<bits/stdc++.h>
using namespace std;
int read()
{
int num=0;bool flag=1;
char c=getchar();
for(;c<'0'||c>'9';c=getchar())
if(c=='-')flag=0;
for(;c>='0'&&c<='9';c=getchar())
num=(num<<1)+(num<<3)+c-'0';
return flag?num:-num;
}
const int N=35;const int M=5010;
int f[N][M],n,m,v1[N][M];
int v2[N][M],sum[N];
struct cow{int x,y;}a[N];
void dfs(int x,int y)
{
if(!x)return ;
dfs(v1[x][y],v2[x][y]);
if(v1[x][y]==x)
for(int i=1;i<=x;i++)sum[a[i].y]++;
else
for(int i=v1[x][y]+1;i<=x;i++)sum[a[i].y]=1;
}
bool cmp(cow x,cow y){return x.x>y.x;}
int main()
{
freopen("_.in","r",stdin);
freopen("_.out","w",stdout);
n=read();m=read();
for(int i=1;i<=n;i++)
a[i].x=read(),a[i].y=i;
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++)
a[i].x+=a[i-1].x;
memset(f,10,sizeof(f));
f[0][0]=0;
for(int i=1;i<=n;i++)
for(int j=i;j<=m;j++)
{
f[i][j]=f[i][j-i];
v1[i][j]=i;v2[i][j]=j-i;
for(int k=0;k<i;k++)
if(f[i][j]>f[k][j-i+k]+(a[i].x-a[k].x)*k)
{
f[i][j]=f[k][j-i+k]+(a[i].x-a[k].x)*k;
v1[i][j]=k;v2[i][j]=j-i+k;
}
}
cout<<f[n][m]<<endl;
dfs(n,m);
for(int i=1;i<=n;i++)
cout<<sum[i]<<' ';
return 0;
}
NO8.逆序对数列
题目描述
对于一个数列{a[i]},如果有i < j且a[i] > a[j],那么我们称ai与aj为一对逆序对数。
若对于任意一个由1~n自然数组成的数列,可以很容易求出有多少个逆序对数。
那么逆序对数为k的这样自然数数列到底有多少个?
输入格式
第一行为两个整数n,k。
输出格式
一个整数,表示符合条件的数列个数,由于这个数可能很大,你只需输出该数对10000求余数后的结果。
样例数据
input
4 1
output
3
样例说明: 下列3个数列逆序对数都为1;分别是1 2 4 3 ;1 3 2 4 ;2 1 3 4;
数据规模与约定
测试数据范围
100%的数据 n <= 2000,k<=20000
时间限制:1s1s
空间限制:256MB
这题首先想到的就是搜索,但是这并不可能(k<=20000)那么我们就设f[i][j]表示1~i个自然数所组合成的、逆序对数为j的数列个数。
在1~i中,i即为最大,故将它安插到某一位置,其后方的所有数都会与它形成逆序对。 由上可得状态转移方程:
f[i][j] = ∑f[i-1][j-i+k],(1 <= k <= i)。
但是这一题还是会超时,但是我们发现k只与i有关,所以我们可以用一个前缀和来记录一下1~i f[i-1][j-i+k]的前缀和就可得到答案
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=2010;
int n,m,f[N][N*10],sum[N*10];
int main()
{
freopen("permut.in","r",stdin);
freopen("permut.out","w",stdout);
cin>>n>>m;
for(int i=1;i<=n;i++)
{
f[i][0]=1;
for(int j=1;j<=m+1;j++)
sum[j]=(sum[j-1]+f[i-1][j-1]+10000)%10000;
for(int j=1;j<=m;j++)
f[i][j]=(sum[j+1]-sum[max(j-i+1,0)]+10000)%10000;
}
cout<<f[n][m];
return 0;
}
No.9养猪
题目描述
你有一个猪圈,有N头猪,每天你最多可以杀一头猪卖钱,收益就是猪的体重。
但是每过一天猪的体重都会下降P[i] (当然,如果猪的体重≤0了,利润自然就是0),问K天内你的最大获利。
输入格式
第一行两个正整数N、K;
第二行N个数表示猪的初始重量A[i];
第三行N个数表示P[i];
输出格式
一行一个整数表示最大的获利。
样例数据
input
2 2
10 10
1 2
output
19
数据规模与约定
20% 数据满足 1≤N≤201≤N≤20
100%数据满足 1≤N≤1000体重≤1051≤N≤1000体重≤105 。
时间限制:1s1s
空间限制:256MB256MB
用贪心易证这个我们要按丢失体重排序便可DP
代码:
#include<bits/stdc++.h>
using namespace std;
int f[41][41][41][41]={},a[351],b[5]={},m,n;
int main()
{
freopen("test.in","r",stdin);
freopen("test.out","w",stdout);
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
int x;for(int i=1;i<=m;i++)cin>>x,b[x]++;
f[1][1][1][1]=a[1];
for(int i=0;i<=b[1];i++)
for(int j=0;j<=b[2];j++)
for(int k=0;k<=b[3];k++)
for(int o=0;o<=b[4];o++)
{
int p=0,pp=0,ppp=0,pppp=0;
if(i>0)p=f[i-1][j][k][o];
if(j>0)pp=f[i][j-1][k][o];
if(k>0)ppp=f[i][j][k-1][o];
if(o>0)pppp=f[i][j][k][o-1];
f[i][j][k][o]=a[i*1+j*2+k*3+o*4+1]+max(p,max(pp,max(ppp,pppp)));
}
cout<<f[b[1]][b[2]][b[3]][b[4]];
return 0;
}