9.6快乐dp(初级1)
作者:互联网
预期 | 实际 | 差值 | |
a | 100 | 83 | -17 |
b | 100 | 100 | 0 |
c | 100 | 100 | 0 |
d | 100 | 100 | 0 |
e | 0 | 0 | 0 |
f | 100 | 100 | 0 |
g | 100 | 67 | -33(样例数字间无空格) |
h | 100 | 0 | -100 |
i | 0 | 0 | 0 |
j | 100 | 0 | 0 |
总和 | 800 | 400 | -400 |
a.石子合并
由题意易得dp转移方程最大值同理
sum为前缀和,如果不是环,就是效率
法一:枚举断点
从一点断开变成一个链来做 效率方,本题可过
法二:倍长环
从任意一点断开然后复制接到末尾可过
#include<bits/stdc++.h>
using namespace std;
int f[205][205],a[205],sum[205];//记得开两倍
int g[205][205];
int an1=0x7f7f7f7f,an2;
int n;
int main(){
scanf("%d",&n);
memset(f,0x3f,sizeof(f));
for(int i=1;i<=n;i++){
scanf("%d",a+i);
a[i+n]=a[i];
}
for(int i=1;i<=n*2;i++){
sum[i]=sum[i-1]+a[i];
f[i][i]=0;
}
for(int len=2;len<=n;len++){
for(int i=1;i+len-1<n*2;i++){
int j=i+len-1;
for(int k=i;k<j;k++){
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1]);
g[i][j]=max(g[i][j],g[i][k]+g[k+1][j]+sum[j]-sum[i-1]);
}
}
}
for(int i=1;i<=n;i++){
an1=min(an1,f[i][i+n-1]);
an2=max(an2,g[i][i+n-1]);
}
printf("%d\n%d",an1,an2);
}
/*
6 6 5 4 2 3 4
*/
b.对抗赛
枚举每一个数的情况,选或不选,但要炸,就考虑对半深搜,具体来说枚举前一半的可能情况,用记录情况和为sum的个数,在枚举后一半,将ans加上,half为
总和的一半,由每一种情况的另一半也记录了,答案要除2;
#include<bits/stdc++.h>
using namespace std;
int f[50000],a[55];
int n,sum,ans,half;
inline void dfs(int x,int s){
if(s>sum) return ;
if(x>half){
f[s]++;
return ;
}
dfs(x+1,s);
dfs(x+1,s+a[x]);
}
inline void dfs2(int x,int s){
if(s>sum) return ;
if(x>n){
ans+=f[sum-s];
return ;
}
dfs2(x+1,s);
dfs2(x+1,s+a[x]);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",a+i);
sum+=a[i];
}
if(sum&1){
cout<<0;
return 0;
}
sum/=2;
half=n/2;
dfs(1,0);
dfs2(half+1,0);
cout<<ans/2;
}
/*
7
1 2 3 4 5 6 7
*/
c.演讲大厅安排
啥也不说先排序,以左端点为第一关键字,右端点为第二关键字
然后考虑转移方程,表示以 第i场为结尾,即第i场必须演讲,可得
然后就过了
#include<bits/stdc++.h>
using namespace std;
struct node{
int l,r;
}p[5050];
bool operator < (node x,node y){
return x.l<y.l || x.l==y.l && x.r<y.r;
}
int n,ans;
int f[5050];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d%d",&p[i].l,&p[i].r);
sort(p+1,p+n+1);
for(int i=1;i<=n;i++)
for(int k=0;k<i;k++)
if(p[k].r<=p[i].l)
f[i]=max(f[i],f[k]+p[i].r-p[i].l);
for(int i=1;i<=n;i++)//枚举寻找答案
ans=max(ans,f[i]);
cout<<ans;
}
d.传纸条
枚举每一点可能的转移情况,注意当前点和上一点的两条路线均不能重合
#include<bits/stdc++.h>
using namespace std;
const int N=56;
int n,m,a[N][N],f[N<<1][N][N];
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&a[i][j]);
for(int i=0;i<n+m-3;i++)
for(int j=1;j<=n && j<=i+1;j++)
for(int k=1;k<=n && k<=i+1;k++){
if(j!=k){
f[i+1][j][k]=max(f[i+1][j][k],f[i][j][k]+a[j][i+3-j]+a[k][i+3-k]);
f[i+1][j+1][k+1]=max(f[i+1][j+1][k+1],f[i][j][k]+a[j+1][i+2-j]+a[k+1][i+2-k]);
}
if(j+1!=k) f[i+1][j+1][k]=max(f[i+1][j+1][k],f[i][j][k]+a[j+1][i+2-j]+a[k][i+3-k]);
if(k+1!=j) f[i+1][j][k+1]=max(f[i+1][j][k+1],f[i][j][k]+a[j][i+3-j]+a[k+1][i+2-k]);
}
cout<<f[n+m-3][n-1][n]<<endl;
return 0;
}
/*
3 3
0 3 9
2 8 5
5 7 0
*/
e.守望者的逃离
直接讲代码
#include<bits/stdc++.h>
using namespace std;
int m,s,t,now=0;
int main() {
cin>>m>>s>>t;
int s1=0,s2=0;
for(int i=1; i<=t; i++) {
s1+=17;//看当前是走路优还是闪现优
if(m>=10) {//有蓝就闪
s2+=60;
m-=10;
}
else m+=4;//没蓝就回蓝
if(s2>s1) s1=s2;//如果闪现优,那前面走路的时间不如全用来闪现
if(s1>s) {
cout<<"Yes"<<endl<<i<<endl;
return 0;
}
}
cout<<"No"<<endl<<s1<<endl;
}
f.矩阵取数游戏
明显每一行不会影响其他行,那对于每一行来说设表示当前还剩i到j的数没取走
可得
#include<bits/stdc++.h>
#define N 40
using namespace std;
const int mm=10;
struct A {
int e[N];
A(int x=0):e() {
for (int i=x; i; i/=10) e[++e[0]]=i%10;
if (!e[0]) e[0]++;
}
void read() {
e[0]=0;
char c=getchar();
while (c<=32) c=getchar();
while (c>32) {
e[++e[0]]=c-'0';
c=getchar();
}
reverse(e+1,e+e[0]+1);
}
void print() {
for (int i=e[0]; i>=1; i--) printf("%d",e[i]);
printf("\n");
}
};
A operator + (const A &a,const A &b) {
A c;
c.e[0]=max(a.e[0],b.e[0]);
for (int i=1; i<=c.e[0]; i++) {
c.e[i]+=a.e[i]+b.e[i];
c.e[i+1]+=c.e[i]/mm;
c.e[i]%=mm;
}
if (c.e[c.e[0]+1]) c.e[0]++;
return c;
}
A operator * (const A &a,const A &b) {
A c;
c.e[0]=a.e[0]+b.e[0];
for (int i=1; i<=a.e[0]; i++)
for (int j=1; j<=b.e[0]; j++) {
c.e[i+j-1]+=a.e[i]*b.e[j];
c.e[i+j]+=c.e[i+j-1]/mm;
c.e[i+j-1]%=mm;
}
while (c.e[0]>1&&!c.e[c.e[0]]) c.e[0]--;
return c;
}
bool operator < (const A &a,const A &b) {
if (a.e[0]!=b.e[0]) return a.e[0]<b.e[0];
for (int i=a.e[0]; i>=1; i--)
if (a.e[i]!=b.e[i]) return a.e[i]<b.e[i];
return 0;
}
bool operator > (const A &a,const A &b) {
return b<a;
}
A f[90][90];
A p[81];
A a[81][81];
int n,m;
A ans;
int main() {
p[1].e[1]=2;
p[1].e[0]=1;
A zero;zero.e[0]=1,zero.e[1]=0;
ans=zero;
for(int i=1;i<=80;i++) p[i+1]=p[i]*p[1];
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
a[i][j].read();
for(int i=1;i<=n;i++){
for(int len=m;len;len--){
for(int l=1;l+len-1<=m;l++){
int r=l+len-1;
f[l][r]=zero;
if(l>1){
A q=f[l-1][r]+a[i][l-1]*p[m-len];
if(f[l][r]<q)
f[l][r]=q;
}
if(r<m){
A q=f[l][r+1]+a[i][r+1]*p[m-len];
if(f[l][r]<q)
f[l][r]=q;
}
}
}
A maxx=zero;
for(int j=1;j<=m;j++){
A q=f[j][j]+a[i][j]*p[m];
if(q>maxx)
maxx=q;
}
ans=maxx+ans;
}
ans.print();
return 0;
}
g.城市交通
逆向思考:现在想要从城市1到达城市11,则只能从城市8、9或10中转过去,如果知道了从城市1到城市8、9和10的最短距离,那么只要把这3个最短距离分别加上这3个城市与城市11之间的距离,再从中取一个最小值即可。这样一来,问题就变成了求城市1到城市8、9、10的最短距离,而这3个子问题与原问题是完全-致的,只是问题的规模缩小了一点。如何求城市1到城市8的最短距离呢?想法与刚才一样,如果知道了城市1到城市4和5的最短距离,那么到城市8的最短距离就是到城市4的最短距离加上5以及到城市5的最短距离加 上5当中较小的那个值。而如何求城市4和5的最短距离呢? ...如此下去,直到求城市1到城市2和3的最短距离,而这个值是已知的(因为城市1和2.3之间有直接的边相连)
#include<bits/stdc++.h>
using namespace std;
int f[111];
int a[111][111];
int n;
char c;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
scanf("%d",&a[i][j]);
}
f[i]=0x3f3f3f3f;
}
f[n]=0;
for(int i=n-1;i;i--)
for(int j=i+1;j<=n;j++)
if(a[i][j] && f[j]!=0x3f3f3f3f)
f[i]=min(f[i],a[i][j]+f[j]);
cout<<f[1];
}
/*
11
0 5 3 0 0 0 0 0 0 0 0
5 0 0 1 6 3 0 0 0 0 0
3 0 0 0 8 0 4 0 0 0 0
0 1 0 0 0 0 0 5 6 0 0
0 6 8 0 0 0 0 5 0 0 0
0 3 0 0 0 0 0 0 0 8 0
0 0 4 0 0 0 0 0 0 3 0
0 0 0 5 5 0 0 0 0 0 3
0 0 0 6 0 0 0 0 0 0 4
0 0 0 0 0 8 3 0 0 0 3
0 0 0 0 0 0 0 3 4 3 0
*/
h.最长公共子序列
终极板题
定义状态f[i][j],表示序列X的前i个元素和序列Y的前j个元素的最长公共子序列长度。状态转移方程为
以上算法时间复杂度为0(len1 xlen2),已经比较优了。空间复杂度可不可以优化呢?可以采用类似于杨辉三角形的做法,采用“滚动”数组,利用两个-维数组分别存放当前行和上一行,进行迭代存储。
#include<bits/stdc++.h>
using namespace std;
int f[2][5050];
int main(){
char a[5050],b[5050];
scanf("%s",a);
scanf("%s",b);
int la=strlen(a),lb=strlen(b);
for(int i=1;i<=la;i++){
for(int j=1;j<=lb;j++){
if(a[i-1]==b[j-1]) f[i&1][j]=f[(i-1)&1][j-1]+1;
else f[i&1][j]=max(f[i&1][j-1],f[(i-1)&1][j]);
}
}
cout<<f[la&1][lb];
}
i.玩具装箱
正解斜率优化,但优化可过,转移方程如下
优化:记录每一次可将法dp(i)更新的最大j,对于dp(i+1)就从j开枚举断点;
#include<bits/stdc++.h>
using namespace std;
long long dp[50010],sum[50010],a[50010];
long long L;
int n;
int main(){
scanf("%d%lld",&n,&L);
for(int i=1;i<=n;i++){
scanf("%lld",a+i);
sum[i]=sum[i-1]+a[i];
}
for(int i=1,k=0;i<=n;i++){
dp[i]=1e18;
for(int j=k;j<i;j++){
if(dp[j]+(sum[i]+i-sum[j]-j-L-1)*(sum[i]+i-sum[j]-j-L-1)<=dp[i]){
dp[i]=dp[j]+(sum[i]+i-sum[j]-j-L-1)*(sum[i]+i-sum[j]-j-L-1);
k=j;
}
}
}
cout<<dp[n];
}
能过的原因可能是因为本来每一次增多都至少增加二的单位长度,对于包含同一段物品的两盒东西差值至少也为二,又加上平方,即便在前某一点选择错了,但最后又通过后面的正确决策将其覆盖
当然还有可能这个做法确实正确,但我太弱无法证明……
斜率优化……没学啊!!!
j.最长公共上升子序列
两个板子的合体,但难度增加了许多
F[i,j]表示与可以构成的以为结尾的LCIS的长度。不妨假设
当时,有F[i,j]=F[i-1,j]
当时,有
在转移过程中,将满足的k构成的集合称为F[i,j]进行状态转移时的决策集合,记为S(i,j)。注意到,在第二层循环j从1增加到m时,第一层循环i是一个定值,这使得条件
是固定的。因此当便变量j增加1时,k的取值范围从变为,即整数j可能会进入新的决策集合、也就是说,我们只需O(1)地检查条件是否满足,已经在决策集合中的数则一定不会被去除:
所以上面的转移方程只需要两层循环即可求解。最终的目标是。
for (int i = 1; i <= n; i++) {
// val是决策集合S(i,j)中f[i-1][k]的最大值
int val = 0;
// j=1时,0可以作为k的取值
if (b[0] < a[i]) val = f[i - 1][0];
for (int j = 1; j <= m; j++) {
if (a[i] == b[j]) f[i][j] = val + 1;
else f[i][j] = f[i - 1][j];
// j即将增大为j+1,检查j能否进入新的决策集合
if (b[j] < a[i]) val = max(val, f[i - 1][j]);
}
}
总结:大部分都还是挺基础的但分差较大原因多是没注意范围
标签:std,return,int,namespace,初级,using,9.6,include,dp 来源: https://blog.csdn.net/int128maxn/article/details/120142395