T1:能量项链
考察知识:区间型动态规划
算法难度:XXX 实现难度:XX
分析:只要分析出状态转移方程就不难实现了
根据题意,我们可以把项链看作矩阵,项链的合并就是矩阵乘法
所以我们定义两个数组x[],y[],x[],y[]分别存矩阵的行列数
首先我们怎么把项链转化为矩阵呢:
for(int i=1;i<=n;i++) scanf("%d",a+i);
for(int i=1;i<=n;i++) x[i]=a[i],y[i]=a[i%n+1];
for(int i=1;i<=n;i++) x[i+n]=x[i],y[i+n]=y[i];
第一行输入项链,第二行将项链转化为矩阵,但是项链是环状的,所以我们用它了两倍长度表示一个环
下面是动态规划一般套路:
定义状态方程:f(i,j)表示在区间[i,j]的矩阵的最大乘法次数
状态转移方程:
状态转移方程解释:我们先分别合并区间[i,k],[k+1,j],最大要做f(i,k)+f(k+1,j)次乘法,之后剩下合并后矩阵i,j要做x[i]*y[k]*y[j]次乘法
答案:
代码:
#include<iostream>
#include<cstdio>
using namespace std;
int a[105],n;
int x[205],y[205],f[202][202];
int dp(int i,int j){
if(j-i<1) return 0;
if(f[i][j]) return f[i][j];
for(int k=i;k<j;k++)
f[i][j]=max(f[i][j],dp(i,k)+dp(k+1,j)+x[i]*y[k]*y[j]);
return f[i][j];
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",a+i);
for(int i=1;i<=n;i++) x[i]=a[i],y[i]=a[i%n+1];
for(int i=1;i<=n;i++) x[i+n]=x[i],y[i+n]=y[i];
int ans=0;
for(int i=1;i<=n;i++) ans=max(ans,dp(i,i+n-1));
printf("%d\n",ans);
return 0;
}
T2:金明的预算方案
考察知识:子集型动态规划
算法难度:XX+ 实现难度:XXX
分析:和背包问题差不多,只不过这道题中物品可能有附件
对于有附件的物品,我们可以考虑是否要附件,或附件要那些
这道题动态规划的实现有两种填表方法:正着填和逆着填
正着填:
#include<cstdio>
#include<algorithm>
using namespace std;
struct goods{
int v,p,q;//price,importance,kind
int cnt,blg1,blg2;
goods(){cnt=0;}
}A[65];
int n,mon;
int f[62][3500];
int main(){
scanf("%d%d",&mon,&n);mon/=10;
for(int i=1;i<=n;i++)
scanf("%d%d%d",&A[i].v,&A[i].p,&A[i].q);
for(int i=1;i<=n;i++){
A[i].v/=10,A[i].p*=A[i].v;
if(!A[i].q) continue;
int pre=A[i].q;
if(A[pre].cnt++) A[pre].blg2=i;
else A[pre].blg1=i;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=mon;j++){
f[i][j]=f[i-1][j];
if(A[i].q) continue;
int V=A[i].v,P=A[i].p;
if(j>=V) f[i][j]=max(f[i][j],f[i-1][j-V]+P);
else continue;
if(A[i].cnt){
int p1=A[i].blg1,p2=A[i].blg2;
V+=A[p1].v,P+=A[p1].p;
if(j>=V) f[i][j]=max(f[i][j],f[i-1][j-V]+P);
if(A[i].cnt>1){
V=V-A[p1].v+A[p2].v,P=P-A[p1].p+A[p2].p;
if(j>=V) f[i][j]=max(f[i][j],f[i-1][j-V]+P);
V+=A[p1].v,P+=A[p1].p;
if(j>=V) f[i][j]=max(f[i][j],f[i-1][j-V]+P);
}
}
}
printf("%d\n",f[n][mon]*10);
return 0;
}
反着填:
#include<cstdio>
#include<algorithm>
using namespace std;
struct goods{
int v,p,q;//price,importance,kind
int cnt,blg1,blg2;
goods(){cnt=0;}
}A[65];
int n,mon;
int f[3500];
int main(){
scanf("%d%d",&mon,&n);mon/=10;
for(int i=1;i<=n;i++)
scanf("%d%d%d",&A[i].v,&A[i].p,&A[i].q);
for(int i=1;i<=n;i++){
A[i].v/=10,A[i].p*=A[i].v;
if(!A[i].q) continue;
int pre=A[i].q;
if(A[pre].cnt++) A[pre].blg2=i;
else A[pre].blg1=i;
}
for(int i=1;i<=n;i++) if(!A[i].q)
for(int j=mon;j>=1;j--){
int V=A[i].v,P=A[i].p;
if(j>=V) f[j]=max(f[j],f[j-V]+P);
if(A[i].cnt){
int p1=A[i].blg1,p2=A[i].blg2;
V+=A[p1].v,P+=A[p1].p;
if(j>=V) f[j]=max(f[j],f[j-V]+P);
if(A[i].cnt>1){
V=V-A[p1].v+A[p2].v,P=P-A[p1].p+A[p2].p;
if(j>=V) f[j]=max(f[j],f[j-V]+P);
V+=A[p1].v,P+=A[p1].p;
if(j>=V) f[j]=max(f[j],f[j-V]+P);
}
}
}
printf("%d\n",f[mon]*10);
return 0;
}
我们可以看到,对于这道题反着填不管是在代码长度,还是在占用空间上都优于正着填
T3:作业调度方案
考察知识:模拟
算法难度:XXX 实现难度:XXX
分析:如果你读懂题意了,这道题就很简单了
直接按照题目要求的操作就可以了(前提要看懂题目的意思,题目甚至已经把算法流程都告诉你了)
这道题的具体实现我们可以用布尔数组表示加工的情况,按照加工队列一个一个操作,修改数组就可以了
代码:
#include<cstdio>
#include<algorithm>
#define F(var,L,R) for(int var=L;var<=R;var++)
int n,m,que[405],Q[22][22],T[22][22],ans;
bool mac[42][802];
bool empty(int obj,int L,int R){//判断区间是否为空
F(i,L,R) if(mac[obj][i]) return false;
return true;
}
int cover(int obj,int start,int len){//寻找需要覆盖的区间
F(i,start,800) if(empty(obj,i,i+len-1)){
F(j,i,i+len-1) mac[obj][j]=true;//修改区间
return i+len-1;
} return -1;
}
int main(){
scanf("%d%d",&m,&n);
F(i,1,n*m) scanf("%d",que+i);
F(i,1,n) F(j,1,m) scanf("%d",&Q[i][j]);
F(i,1,n) F(j,1,m) scanf("%d",&T[i][j]);
F(i,1,n*m){
int cnt=++Q[que[i]][0];//第cnt道工序
T[que[i]][cnt]=cover(Q[que[i]][cnt],T[que[i]][cnt-1]+1,T[que[i]][cnt]);
}
F(i,1,n) ans=std::max(ans,T[i][m]);
printf("%d\n",ans);
return 0;
}
T4:2^k进制数
考察知识:高精度,递推,数学
算法难度:XXX 实现难度:XXXX
分析:你可以把k<=3,w<=21时候对应的值做成表,然后你就会发现它就是杨辉三角形的一部分,你可以考虑用排列组合求方案数
但是我要讲另一种递推方法:
表示第1位为j且长度为i的2^k进制数的个数
递推方程:
边界:
但是实现这个方程有很多细节,不如为了不MLE要有滚动数组,等等
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
struct bign{
int a[205],len;
bign(){a[0]=0,len=1;memset(a,0,sizeof(a));}
void get_v(int v){
bign(); if(!v) return; len=0;
while(v) a[len++]=v%10,v/=10;
}
friend bign operator + (bign A,bign B){
if(A.len<B.len) swap(A,B);
for(int i=0;i<B.len;i++) A.a[i]+=B.a[i];
A.a[A.len]=0;
for(int i=0;i<A.len;i++) if(A.a[i]>9) A.a[i]-=10,A.a[i+1]++;
if(A.a[A.len]) A.len++;
return A;
}
void out(bool Entr=true){
for(int i=len-1;i>=0;i--) putchar('0'+a[i]);
if(Entr) putchar('\n');
}
}f[2][512],f_[512],ans;
int k,w;
bool ALL;
int main(){
scanf("%d%d",&k,&w);
int n=w/k+1,m=1<<k;
if(n>(1<<k)-1) n=(1<<k)-1,ALL=true;
for(int i=1;i<m;i++) f[1][i].get_v(1);
for(int i=2;i<=n;i++){//递推
f[i%2][m-i].get_v(1);
if(ALL||i<n) ans=ans+f[i%2][m-i];
for(int j=m-i-1;j>0;j--){
f[i%2][j]=f[i%2][j+1]+f[(i-1)%2][j+1];
if(ALL||i<n) ans=ans+f[i%2][j];
}
}
if(n!=1&&!ALL)//处理
for(int i=min((1<<(w%k))-1,m-n);i>0;i--)
ans=ans+f[n%2][i];
ans.out();
return 0;
}