9.7快乐dp2

预期一测

\Delta

a1001000
b1000-100
c1001000
d1001000
e1001000
f1001000
g000
h1001000
总分700600-100

 

问题 A: 火车站(Noip1998)

由题意得目标主要求第二站上车人数,设为b,本题又包含fib数列的性质,题上又说是int范围内,众所周知即便是fib这样前两项都是1的数列到第40项就爆int了,所以直接二分检查mid是否可以使最后一站人数刚好为m多了或中途就比m大或爆int(加成负数)就舍弃右区间,少了就舍弃左区间,最后在检查左端点或右端点是否合法,合法就顺推求第x站,不合法就输出“No answer.”

在检查时可以用一个数组f记录每一站上车人数,前4站没有规律,需分别手动赋值为a,0,a,b;

#include<bits/stdc++.h>
using namespace std;
int f[50];
int a,n,m,x;
int check(int b){
	f[1]=a;if(n==1) return a; 
	f[2]=0;if(n==2) return a;
	f[3]=a;if(n==3) return a+a;
	f[4]=b;if(n==4) return a+a+b;
	int sum=a+a+b;
	for(int i=5;i<=n-1;i++){
		f[i]=f[i-1]+f[i-2];
		if(f[i]<0) return -1;
		sum+=f[i];
		if(sum>m || sum<0) return -1;
	}
	return sum;
}
int main(){
	scanf("%d%d%d%d",&a,&n,&m,&x);
	int l=0,r=m,ans=0;
	while(l<r){
		int mid=(l+r)/2;
		int num=check(mid);
		if(num==-1) r=mid;//-1代表比m大
		else if(num<m) l=mid+1;
		else {ans=mid;break;}
	}
	if(check(l)==m)	ans=l;
	if(check(ans)==m){
		int sum=0;
		for(int i=1;i<=x;i++) sum+=f[i];
		cout<<sum;
	} else {cout<<"No answer.";return 0;}
}
/*
1 6 7 6

*/

问题 B: 最大的子序列和(maxsum)

本是一个十分简单的小贪心但一千万的数据和仅64兆的内存限制使得这道题需要用快读不能开数组

#include<bits/stdc++.h>
using namespace std;
template <typename T>inline void read(T &x){
	T ch = getchar(), xx = 1; x = 0;
	while(!isdigit(ch)) xx = ch == '-' ? -1 : xx, ch = getchar();
	while(isdigit(ch)) x = (x<<3) + (x<<1) + ch - '0', ch = getchar();
	x *= xx;
}
long long a[2];
int n;
long long ans=-99999999;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		read(a[i&1]);
		a[i&1]=max(a[(i-1)&1]+a[i&1],a[i&1]);
		ans=max(ans,a[i&1]);
	}
	cout<<ans;
}
/*
10
3 1 -6 1 7 5 -2 5 -100 10

*/

问题 C: 合唱队形(chorus)

小板题,正着来一次lcs倒着来一次lcs在找答案就行了

#include<bits/stdc++.h>
using namespace std;
int n,p[1010],ans;
int a[1010],b[1010];
inline void work(int x[1010],int y[1010]){
	for(int i=1;i<=n;i++) y[i]=1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<i;j++)
			if(x[j]<x[i] && y[j]+1>y[i])
				y[i]=y[j]+1;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",p+i);
	work(p,a);
	for(int i=1;i<=n;i++) b[i]=p[n-i+1];
	work(b,p);
	for(int i=1;i<=n;i++) ans=max(ans,a[i]+p[n-i+1]-1);
	cout<<n-ans;
}
/*
8
186 186 150 200 160 130 197 220

*/

问题 D: 数字组合

背包板题

#include<bits/stdc++.h>
using namespace std;
int n,m,p[111],ans;
long long f[10010];
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",p+i);
	f[0]=1;
	for(int i=1;i<=n;i++)
		for(int j=m;j>=p[i];j--)
			f[j]+=f[j-p[i]];
	cout<<f[m];
}
/*
4 4
1 1 2 2 

*/

问题 E: 乌龟棋

这个线性dp注意的是不能存到达位置的答案,而是要存用牌情况的答案,因为一种位置可由多种方式得到,若存到达位置的答案在转移途中就会导致一种更优情况就被重复计算。而存用牌情况的话就会使得转移方案唯一避免被重复计算

#include<bits/stdc++.h>
using namespace std;
int n,m,t,a[355],c[5],f[44][44][44][44];
int main() {
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",a+i);
	for(int i=1;i<=m;i++) {
		scanf("%d",&t);
		c[t]++;
	}
	for(int x=0;x<=c[1];x++)
		for(int y=0;y<=c[2];y++)
			for(int z=0;z<=c[3];z++)
				for(int i=0;i<=c[4];i++) {
					int w=x+y+y+z+z+z+i+i+i+i;
					if(i) f[i][x][y][z]=max(f[i][x][y][z],f[i-1][x][y][z]);
					if(x) f[i][x][y][z]=max(f[i][x][y][z],f[i][x-1][y][z]);
					if(y) f[i][x][y][z]=max(f[i][x][y][z],f[i][x][y-1][z]);
					if(z) f[i][x][y][z]=max(f[i][x][y][z],f[i][x][y][z-1]);
					f[i][x][y][z]+=a[w+1];
				}
	printf("%d",f[c[4]][c[1]][c[2]][c[3]]);
	return 0;
}
/*
9 5
6 10 14 2 8 8 18 5 17
1 3 1 2 1

*/

问题 F: 低买

并不是一道简单的最长不上升子序列,因为他要求求方案数,而且如果两种方案的买入日序列不同,但是价格序列相同,则认为这是相同的方案(只计算一次)。也就是说4 3 2 1 1 的方案数只算一种,其实只要在最长不上升子序列加几句就可以避免重复甚至更快

for(int i=1;i<=n;i++){
		for(int j=i-1;j>=0;j--)
			if(a[j]>a[i]) f[i]=max(f[i],f[j]+1);
		for(int j=i-1;j>=0;j--){
			if(a[j]>a[i] && f[i]==f[j]+1) add(i,j);//可以理解为ans[i]+=ans[j]
			if(a[i]==a[j] && f[i]==f[j]) break;
		}
	}

求i方案的j是从i-1倒着继承给i,满足i的继承条件就直接继承,遇到跟i相同的j就直接break掉,这样的话i就不会继承j前面的,因为j前面的已被j继承,就不需要由i继承,i只继承j+1~i的就行了,最后直接把跟最大值相同的方案数加起来就行了。完整代码如下

#include<bits/stdc++.h>
using namespace std;
int n,a[5006],f[5006],d[5006][800];
void add(int x, int y){//gaojingjia
	d[x][0]=max(d[x][0],d[y][0]);
	for(int i=1;i<=d[x][0];i++) d[x][i]=d[x][i]+d[y][i];
	for(int i=1;i<=d[x][0];i++) {
		d[x][i+1]+=d[x][i]/10;
		d[x][i]%=10;
	}
	if(d[x][d[x][0]+1]>0) d[x][0]++;
}
int main() {
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",a+i); 
	d[0][0]=d[0][1]=1;
	a[0]=0x7fffffff;
	for(int i=1;i<=n;i++){
		for(int j=i-1;j>=0;j--)
			if(a[j]>a[i]) f[i]=max(f[i],f[j]+1);
		for(int j=i-1;j>=0;j--){
			if(a[j]>a[i] && f[i]==f[j]+1) add(i,j);
			if(a[i]==a[j] && f[i]==f[j]) break;
		}
	}
	int z=0;
	for(int i=1;i<=n;i++) z=max(z,f[i]);
	for(int i=1;i<=n;i++) if(f[i]==z) add(5005,i);
	printf("%d ",z);
	for(int i=d[5005][0];i;i--) printf("%d",d[5005][i]);
	return 0;
}
/*
200
199 200 197 198 195 196 193 194 191 192 189 190 187 188 185 186 183 184 181 182 179
180 177 178 175 176 173 174 171 172 169 170 167 168 165 166 163 164 161 162 159 160
157 158 155 156 153 154 151 152 149 150 147 148 145 146 143 144 141 142 139 140 137
138 135 136 133 134 131 132 129 130 127 128 125 126 123 124 121 122 119 120 117 118
115 116 113 114 111 112 109 110 107 108 105 106 103 104 101 102 99 100 97 98 95 96
93 94 91 92 89 90 87 88 85 86 83 84 81 82 79 80 77 78 75 76 73 74 71 72 69 70 67 68
65 66 63 64 61 62 59 60 57 58 55 56 53 54 51 52 49 50 47 48 45 46 43 44 41 42 39 40
37 38 35 36 33 34 31 32 29 30 27 28 25 26 23 24 21 22 19 20 17 18 15 16 13 14 11 12
9 10 7 8 5 6 3 4 1 2

上面是一个毒瘤数据,答案
    100 1267650600228229401496703205376
    可超long long的平方
5
4 3 2 1 1

*/

注:数据可以很毒瘤,甚至使答案需要用高精,不过呢int就可过

问题 G: 炮兵阵地

经典状压dp,直接上代码

#include<iostream>
#include<cstdio>
using namespace std;
int n,m;
int s[70],g[70],k;//储存状态和相应状态“1”的个数(即可放多少人)
int f[102][70][70],ans;//分别为行数,上一行状态,这一行状态,而实测可行状态仅有60来种
char ma[11];
int a[103];
int get(int x){
	int ret=0;
	while(x>0){++ret;x-=x&(-x);}//lowbit求“1”的个数
	return ret;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		scanf("%s",ma);
		for(int j=0;j<m;j++)
			if(ma[j]=='H') a[i]+=1<<j;
	}
	m=1<<m;
	for(int i=0;i<m;i++)
	  if(!(i&(i<<1) || i&(i<<2) || i&(i>>1) || i&(i>>2))){//枚举可行状态
			s[++k]=i;
			g[k]=get(i);
			if(!(i&a[1])) f[1][0][k]=g[k];//初始化第一行
		} 
	for(int i=1;i<=k;i++)
		for(int j=1;j<=k;j++)
			if(!((s[i]&s[j]) || (s[j]&a[2])))
				f[2][i][j]=f[1][0][i]+g[j];//初始化第二行
	for(int i=3;i<=n;i++)//i从3开始
		for(int j=1;j<=k;j++)//这一行状态
		if(!(a[i]&s[j]))//这一行不能和地形冲突
			for(int p=1;p<=k;p++)//上一行
			if(!(s[p]&s[j] || s[p]&a[i-1]))//上一行不能和上一行地形,这一行冲突
				for(int q=1;q<=k;q++)//上上一行
				if(!((s[q]&s[p]) || (s[q]&s[j]) || s[q]&a[i-2]))//同理
					f[i][p][j]=max(f[i][p][j],f[i-1][q][p]+g[j]);
	for(int i=1;i<=k;i++)
		for(int j=1;j<=k;j++)
			ans=max(f[n][i][j],ans);
	cout<<ans;
    return 0;
}
/*
这样仅有100*60*60*60(肯定会更少)的时间复杂度
*/

问题 H: 数字金字塔

dp入门题

#include<bits/stdc++.h>
using namespace std;
int a[1001][1001],n;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=i;j++)
			scanf("%d",&a[i][j]); 
	for(int i=n;i>1;i--)
		for(int j=1;j<i;j++)
			a[i-1][j]=max(a[i][j],a[i][j+1])+a[i-1][j];
	cout<<"max="<<a[1][1];
}
/*
5
13
11 8
12 7 26
6 14 15 8
12 7 13 24 11

*/

 总结:与昨天的题难度差不多甚至得分比昨天好?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值