dp令人头秃
A - 必做题 - 1
题目描述
给出n个数,zjm想找出出现至少(n+1)/2次的数, 现在需要你帮忙找出这个数是多少?
题目分析
- 至少出现(n+1)/2次——中位数
- 排序,取(n+1)/2位置的数输出即可
代码
1 |
|
B - 必做题 - 2
题目描述
zjm被困在一个三维的空间中,现在要寻找最短路径逃生!
空间由立方体单位构成。
zjm每次向上下前后左右移动一个单位需要一分钟,且zjm不能对角线移动。
空间的四周封闭。zjm的目标是走到空间的出口。
是否存在逃出生天的可能性?如果存在,则需要多少时间?
题目分析
- 注意是立体的六个方向,上下左右前后
- 宽度优先遍历即可,注意记录步数
代码
1 |
|
C - 必做题 - 3
题目描述
东东每个学期都会去寝室接受扫楼的任务,并清点每个寝室的人数。
每个寝室里面有ai个人(1<=i<=n)。从第i到第j个宿舍一共有sum(i,j)=a[i]+…+a[j]个人
这让宿管阿姨非常开心,并且让东东扫楼m次,每一次数第i到第j个宿舍sum(i,j)
问题是要找到sum(i1, j1) + … + sum(im,jm)的最大值。且ix <= iy <=jx和ix <= jy <=jx的情况是不被允许的。也就是说m段都不能相交。
注:1 ≤ i ≤ n ≤ 1e6 , -32768 ≤ ai ≤ 32767 人数可以为负数。。。。(1<=n<=1000000)
输入
输入m,输入n。后面跟着输入n个ai 处理到 EOF
输出
输出最大和
题目分析
先考虑了贪心做法:划分负数和正数段,判断m与正数段的关系;然而m<正数段总个数时,无法判定是通过添加负数段使得相邻的正数段连起来or取最大的m个正数段。
然后考虑dp:定义状态
dp[i][j][0/1]
为:到第i个班级,分成j段,选/不选第i个的最大值。转移方程:
当前不选 ->前一个选/不选
dp[i][j][0] = max( dp[i-1][j][0],dp[i-1][j][1]) );
当前选 ->开新段(前面选/不选)/ 合并到前一段
dp[i][j][1] = max(dp[i-1][j-1][1], dp[i-1][j-1][0] ,dp[i-1][j][1]) +a[i]);
后两维用滚动数组就只有2*2大小。然而并不对。原因是:仅凭左边一个无法确定当前状态。
因为当前状态的确定依赖于
j-1
段的所有状态,所以重新定义dp[i][j]
为:到第i个班级,分成j段,同时选第i个的最大值。新的转移方程:
开新段(从i之前位置的j-1段挑最大的)/ 合并到前一段
dp[i][j] = max( max(dp[k][j-1])(0<k<i) , dp[i-1][j-1]) + a[i];
发现每次都是从
j-1
状态转来,那么可以直接抹去这个维度。每次都要计算位置i
之前最大的,那可以直接开个变量每次更新最大值,增加一个pre数组记录每个位置之前的最大值,更新下一段的时候用。
代码
1 |
|
D - 选做题 - 1
题目描述
给出一组括号序列,算出最长匹配的子序列的长度
题目分析
- 区间dp,注意要左端点要反向遍历,否则无法使用之前更新好的状态。
- 转移方程:
dp[i][j] = max(dp[i][j] , dp[i][k],dp[k+1][j]);
if(s[i] matches s[j]) dp[i][j] = dp[i+1][j-1]+2
代码
1 |
|
E - 选做题 - 2
题目描述
马上假期就要结束了,zjm还有 n 个作业,完成某个作业需要一定的时间,而且每个作业有一个截止时间,若超过截止时间,一天就要扣一分。
zjm想知道如何安排做作业,使得扣的分数最少。
Tips: 如果开始做某个作业,就必须把这个作业做完了,才能做下一个作业。
输入
有多组测试数据。第一行一个整数表示测试数据的组数
第一行一个整数 n(1<=n<=15)
接下来n行,每行一个字符串(长度不超过100) S 表示任务的名称和两个整数 D 和 C,分别表示任务的截止时间和完成任务需要的天数。
这 n 个任务是按照字符串的字典序从小到大给出。
输出
每组测试数据,输出最少扣的分数,并输出完成作业的方案,如果有多个方案,输出字典序最小的一个。
题目分析
观察到作业数量很少,因此可以考虑状压dp
由于题目要求按照字符串从小到大给出结果,因此我们选择将所有课程先按字典序排序。
然后考虑将作业状压:完成置1,未完成置0,转移方程:
dp[ i ] = dp[ i-bit[j] ] + max( sum - task[j].ddl , 0 ) ;
其中
i
表示某个状态,j
表示作业号,bit[j]
是该作业号对应的位,sum
是状态i
消耗的总时间,task[j].ddl
是任务j
的ddl,先然超前完成不可能扣分,因此和0取个max。整个方程的意思就是:当前状态是由所有和当前状态差一个作业的状态转移而来。
期间还要输出顺序,pre数组直接记录某个状态要完成的最后一个作业号就好。递归输出
代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
using namespace std;
struct rec{
int ddl,c;
string name;
// void init(int d,int c,string name):ddl(d),c(c),name(name){}
}task[20];
int n;
int bit[20],pre[700000];
int dp[700000];
bool cmp(const rec &a,const rec &b){
return a.name < b.name;
}
void getPre(int x){
if(x != 0){
// cout<<bitset<sizeof(int)*8>(x)<<" "<<pre[x]<<"\n";
getPre(x-bit[pre[x]]);
cout<<task[pre[x]].name<<"\n";
}else return;
}
int main(){
int T;
cin>>T;
bit[0] = 1;
for(int i = 1;i<=15;i++){
bit[i] = bit[i-1] << 1;
}
while(T--){
cin>>n;
memset(dp,63,sizeof(dp));
dp[0] = 0;
for(int i = 0;i<n;i++){
cin>>task[i].name>>task[i].ddl>>task[i].c;
dp[1<<i] = max(task[i].c - task[i].ddl,0) ;
}
sort(task,task+n,cmp);
for(int i = 1;i <= (1<<n)-1;i++){
int sum = 0;
for(int j = 0;j<n;j++){
if(bit[j] & i) sum += task[j].c;
}
// cout<<bitset<sizeof(int)*8>(i)<<" "<<sum<<"\n";
for(int j = 0;j<n;j++){
if(bit[j] & i){
// dp[i] = min(dp[i],dp[i-bit[j]] + max( sum + task[j].c-task[j].ddl, 0));
if(dp[i] >= dp[i-bit[j]] + max( sum - task[j].ddl , 0)){
dp[i] = dp[i-bit[j]] + max( sum - task[j].ddl , 0);
pre[i] = j;
}
}
}
}
cout<<dp[(1<<n)-1]<<"\n";
getPre((1<<n)-1);
}
}