今天开始学习DP 这是个奇妙的过程 。从磕入门开始。呃...导弹拦截和数字三角形还是比较好理解的
然后就是导弹拦截的练习题合唱队形。
LUOGU1091 合唱队形
题目描述:N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK, 则他们的身高满足T1<...<Ti>Ti+1>…>TK(1<=i<=K)。你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形
思路和导弹拦截差不多 一遍正搜一遍反搜 正搜:以第i人为最高的中间人 则f1[i]为他左边需要删掉的人。反搜 相反 f2[i]为他右边需要删掉的人 然后再把两个值加起来就为第i人为最高时需要出列的同学数量 。将最小值输出即可。我的代码如下:
#include<bits/stdc++.h>
using namespace std;
int f1[200]={0},f2[200]={0},f[200]={0};
int main()
{
int i,j,n,min;
scanf("%d",&n);
int a[n+1];
bool flag;
for(i=1;i<=n;i++) scanf("%d",&a[i]);
for(i=2;i<=n;i++)
{
f1[i]=i;
flag=true;
for(j=1;j<i;j++)
if ((a[j]<a[i])&&((f1[j]+i-j-1)<f1[i])) f1[i]=f1[j]+i-j-1,flag=false;
if(flag) f1[i]=i-1;
}
for(i=n-1;i>=1;i--)
{
f2[i]=n-i+10;
flag=true;
for(j=n;j>i;j--)
if ((a[j]<a[i])&&((f2[j]+j-i-1)<f2[i])) f2[i]=f2[j]+j-i-1,flag=false;
if(flag) f2[i]=n-i;
}
min=100;
for(i=1;i<=n;i++)
{
f[i]=f1[i]+f2[i];
if(f[i]<min) min=f[i];
}
cout<<min;
return 0;
}
看完了动归基础 先做一题练习题
LUOGU1280 尼克的任务
题目描述尼克每天上班之前都连接上英特网,接收他的上司发来的邮件,这些邮件包含了尼克主管的部门当天要完成的全部任务,每个任务由一个开始时刻与一个持续时间构成。尼克的一个工作日为N分钟,从第一分钟开始到第N分钟结束。当尼克到达单位后他就开始干活。如果在同一时刻有多个任务需要完戍,尼克可以任选其中的一个来做,而其余的则由他的同事完成,反之如果只有一个任务,则该任务必需由尼克去完成,假如某些任务开始时刻尼克正在工作,则这些任务也由尼克的同事完成。如果某任务于第P分钟开始,持续时间为T分钟,则该任务将在第P+T-1分钟结束。写一个程序计算尼克应该如何选取任务,才能获得最大的空暇时间。
这题是把我难坏了......先是按照之前的思路 。前i个任务的最优值。循环到后面发现多个任务重复在同一时间将会很难处理.....试了很多办法也没办法处理这个问题....也就是顺推推不出来(反正我没推出来= =)于是按照舒婧大佬的思路,从最后一个时间点开始 如果这个时间点不是某个任务的开始点则视为休息。用S【i】存储 如果有任务则S【i】=S【结束的时间点】+1。多个任务重复在一个时间点就比哪个能休息的时间长.代码如下:#include<bits/stdc++.h>
using namespace std;
int n,k,s[10010]={0};
struct work
{
int begin,end,time;
};
work w[10010];
void init()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=k;i++)
{
scanf("%d%d",&w[i].begin,&w[i].time);
w[i].end=w[i].begin+w[i].time-1;
}
return;
}
bool mycmp(work x,work y)
{
return(x.begin<y.begin);
}
int main()
{
init();
sort(w+1,w+k+1,mycmp);
for(int i=n;i>=1;i--)
{
bool flag=false;
for(int j=1;j<=k;j++)
if(w[j].begin==i )s[i]=max(s[i],s[w[j].end+1]),flag=true;
if(flag==false)s[i]=s[i+1]+1;
}
printf("%d",s[1]);
return 0;
}
.后面就是一些资源分配类的题目 两个例题分别是机器分配P1263和复制书稿P1264 花了一会时间也算是理解了
嗑完入门就是背包问题了 嗑完了入门01背包就比较容易理解了 然而01背包洛谷里面的练习....
第一题采药
LUOGU1048 采药
题目描述辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”如果你是辰辰,你能完成这个任务吗?
第一题 还算是简单 和01背包差别不大 按照01背包的思路很容易
第二题
这是一个神奇的题目....很难受 这让我明白了学dp 从看不懂到看懂 看懂到理解 理解到应用是艰难的过程.....这道题看了题解我也没能理解他的状态转移方程(f[j]=f[j]+f[j-i])是什么原理.....反正思考了很久 求助了刘老师看了一些大佬 的笔记文章还是有点迷迷糊糊 不能理解他的原理... 后来可能是多年攒下的rp爆发了 灵光一闪 想到了这是特殊的01背包....不过是特殊到能把数字代到01背包里面的这个代码:
LUOGU1466 集合 Subset Sums
题目描述对于从1到N (1 <= N <= 39) 的连续整数集合,能划分成两个子集合,且保证每个集合的数字和是相等的。举个例子,如果N=3,对于{1,2,3}能划分成两个子集合,每个子集合的所有数字和是相等的:{3} 和 {1,2}这是唯一一种分法(交换集合位置被认为是同一种划分方案,因此不会增加划分方案总数) 如果N=7,有四种方法能划分集合{1,2,3,4,5,6,7},每一种分法的子集合各数字和是相等的给出N,你的程序应该输出划分方案总数,如果不存在这样的划分方案,则输出0。程序不能预存结果直接输出(不能打表)。
这是一个神奇的题目....很难受 这让我明白了学dp 从看不懂到看懂 看懂到理解 理解到应用是艰难的过程.....这道题看了题解我也没能理解他的状态转移方程(f[j]=f[j]+f[j-i])是什么原理.....反正思考了很久 求助了刘老师看了一些大佬 的笔记文章还是有点迷迷糊糊 不能理解他的原理... 后来可能是多年攒下的rp爆发了 灵光一闪 想到了这是特殊的01背包....不过是特殊到能把数字代到01背包里面的这个代码:procedure ZeroOnePack(cost,weight)
for v=V..cost
f[v]=max{f[v],f[v-cost]+weight}
和for i=1..N
ZeroOnePack(c[i],w[i]);
一代 就得到了题解里面的: for(int i=1;i<=n;i++)
for(int j=t;j>=i;j--)
f[j]=f[j]+f[j-i];
然后再理解就不是那么难了。
procedure ZeroOnePack(cost,weight)
for v=V..cost
f[v]=max{f[v],f[v-cost]+weight}
for i=1..N
ZeroOnePack(c[i],w[i]);
for(int i=1;i<=n;i++)
for(int j=t;j>=i;j--)
f[j]=f[j]+f[j-i];
然后就是02背包.02背包和01还是有区别的。同一种物体可以重复用 那么有些价值高的就可以多拿了 而且这就不用像01背包一样怕物体重复拿而逆推。直接顺还解决了重复拿的问题......