[DP] DP随练随学(疯狂A题训练——DP基础篇 题解 中)

T18 Chain Reaction

传送门

这题好像做过

f i f_i fi表示能保留的塔个数,那么有:

f i = { f i − 1 i 位 置 没 有 激 光 塔 1 i ≤ b i f i − b i − 1 + 1 i > b i f_i= \left\{ \begin{aligned} &f_i-1&&i位置没有激光塔\\ &1&&i\leq b_i\\ &f_{i-b_i-1}+1&&i>b_i \end{aligned} \right. fi=fi11fibi1+1iibii>bi

a n s = n − max ⁡ 1 ≤ i ≤ l e n f i ans=n-\max_{1\le i\le len}f_i ans=n1ilenmaxfi

注意边界
注意空间,直接开数组估计要炸
换成map挺好

#include<bits/stdc++.h>
using namespace std;
#define in Read()
int in{
	int i=0,f=1;char ch=0;
	while(!isdigit(ch)&&ch!='-') ch=getchar();
	if(ch=='-') ch=getchar(),f=-1;
	while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int N=1e6+5;
int n,maxn,f[N],ans;
struct Data{
	int a,b;
}a[N];
map<int,bool>mp;
map<int,int>Pow;

bool cmp(Data u,Data v){
	return u.a<v.a;
}

int main(){
    n=in;
	for(int i=1;i<=n;++i) a[i].a=in,a[i].b=in,maxn=max(maxn,a[i].a);
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;++i){
		mp[a[i].a]=true;
		Pow[a[i].a]=a[i].b;
		maxn=max(maxn,a[i].a);
	}
	if(mp[0]) f[0]=1;
	for(int i=0;i<=maxn;++i){
		if(mp[i]){
			if(Pow[i]>=i) f[i]=1;
			else f[i]=f[i-Pow[i]-1]+1;
		}else f[i]=f[i-1];
		ans=max(ans,f[i]);
	}
	printf("%d\n",n-ans);
	return 0;
}

Top

T19 the Monetary System

传送门

我很菜的时候见过这题
当然现在还是不会

显然我们要去掉原系统中由小面值能得到大面值的
这样才能构成合法的新系统
若新系统中有原系统没有且无法表示的货币,新系统显然不合法

考虑背包,用已确认不能删的更新未得到的

这题数据范围是假的
数组要往大了开


#include<bits/stdc++.h>
using namespace std;
#define in Read()
int in{
	int i=0,f=1;char ch=0;
	while(!isdigit(ch)&&ch!='-') ch=getchar();
	if(ch=='-') ch=getchar(),f=-1;
	while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int N=1e5+5;
int n,a[N],f[N],cnt;

void solve(){
	memset(a,0,sizeof(a));
	memset(f,0,sizeof(f));
	n=in,cnt=n,f[0]=1;
	for(int i=1;i<=n;++i) a[i]=in;
	sort(a+1,a+n+1);
	for(int i=1;i<=n;++i){
		if(f[a[i]]){
			--cnt;
			continue;
		}
		for(int j=a[1];j<=a[n];++j) f[j]|=f[j-a[i]];
	}
	printf("%d\n",cnt);
	// for(int i=1;i<=a[n];++i) if(f[i]) cout<<i<<" ";cout<<endl;
	// for(int i=1;i<=cnt;++i) cout<<b[i]<<" ";cout<<endl;
	return;
}

int main(){
	int T=in;
	while(T--) solve();
	return 0;
}

Top

T20 精卫填海

传送门

0/1背包

#include<bits/stdc++.h>
using namespace std;
#define in Read()
int in{
	int i=0,f=1;char ch=0;
	while(!isdigit(ch)&&ch!='-') ch=getchar();
	if(ch=='-') ch=getchar(),f=-1;
	while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int N=1e4+5;
const int INF=2147483647;
int v,n,c;
int vol[N],wei[N],f[N];

int main(){
	v=in,n=in,c=in;
	for(int i=1;i<=n;++i) vol[i]=in,wei[i]=in;
	for(int i=1;i<=v;++i) f[i]=INF;f[0]=0;
	for(int i=1;i<=n;++i)
		for(int j=v;j>=1;--j)
			if(f[j-vol[i]]!=INF)
				f[j]=min(f[j],f[j-vol[i]]+wei[i]);
	if(f[v]>c) puts("Impossible");
	else printf("%d\n",c-f[v]);
	return 0;
}

Top

T21 [AHOI2001]质数和分解

传送门

哈!
这道题可以用生成函数做!

F = ∑ i = 1 ∞ [ i ∈ P r i m e ] x i a n s = [ x n ] ∑ i = 0 ∞ F i = [ x n ] 1 1 − F F=\sum_{i=1}^\infin [i\in \mathrm{Prime}]x^i\\ ans=[x^n]\sum_{i=0}^\infin F^i=[x^n]\frac{1}{1-F} F=i=1[iPrime]xians=[xn]i=0Fi=[xn]1F1

当然这道题200的数据范围是没必要瞎搞的
递推就行
下面是一个低级至极的代码

#include<bits/stdc++.h>
using namespace std;
#define in Read()
int in{
	int i=0,f=1;char ch=0;
	while(!isdigit(ch)&&ch!='-') ch=getchar();
	if(ch=='-') ch=getchar(),f=-1;
	while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int N=1000;
int n,a[N],cnt,maxn,f[N];
int p[N],num;

bool pd(int x){
	for(int i=2;i<=sqrt(x);++i)
		if(x%i==0) return false;
	return true;
}

int main(){
	while(cin>>n){
		int num=0;
		for(int i=2;i<=n;++i)
			if(pd(i)) p[++num]=i;
		memset(f,0,sizeof(f));
		f[0]=1;
		for(int i=1;i<=num;++i)
			for(int j=p[i];j<=n;++j)
				f[j]+=f[j-p[i]];
		printf("%d\n",f[n]);
	}
	return 0;
}

Top

T22 疯狂的采药

传送门

啊!
菜鸡痛苦的回忆

最后发现省一维空间就行了

#include<bits/stdc++.h>
using namespace std;
#define in Read()
int in{
	int i=0,f=1;char ch=0;
	while(!isdigit(ch)&&ch!='-') ch=getchar();
	if(ch=='-') ch=getchar(),f=-1;
	while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int N=1e5+5;
int t,m,v[N],w[N],f[N];

int main(){
	t=in,m=in;
	for(int i=1;i<=m;++i) v[i]=in,w[i]=in;
	for(int i=1;i<=m;++i)
		for(int j=v[i];j<=t;++j)
			f[j]=max(f[j],f[j-v[i]]+w[i]);
	printf("%d",f[t]);
	return 0;
}

top

T23 A+B Problem(再升级)

传送门

大水题
又不能积累到筛法经验qwq

#include<bits/stdc++.h>
using namespace std;
#define in Read()
#define int long long 
int in{
	int i=0,f=1;char ch=0;
	while(!isdigit(ch)&&ch!='-') ch=getchar();
	if(ch=='-') ch=getchar(),f=-1;
	while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int N=1e5+5;
int n,p[N],cnt,f[N];

bool check(int x){
	for(int i=2;i<=sqrt(x);++i)
		if(x%i==0) return false;
	return true;
}

void prime(int lim){
	for(int i=2;i<=lim;++i)
		if(check(i)) p[++cnt]=i;
	return;
}

signed main(){
	n=in;
	prime(n);
	f[0]=1;
	for(int i=1;i<=cnt;++i)
		for(int j=p[i];j<=n;++j)
			f[j]+=f[j-p[i]];
	printf("%lld\n",f[n]);
	// for(int i=1;i<=n;++i) cout<<f[i]<<" ";cout<<endl;
	return 0;
}

果然水题
注意一下数据范围就行了

top

T24 最大约数和

传送门

好多数论水题

#include<bits/stdc++.h>
using namespace std;
#define in Read()
int in{
	int i=0,f=1;char ch=0;
	while(!isdigit(ch)&&ch!='-') ch=getchar();
	if(ch=='-') ch=getchar(),f=-1;
	while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int N=1e5+5;
int n,sum[N],f[N];

int main(){
	n=in;
	for(int i=1;i<=n;++i)
		for(int j=1;j<i;++j)
			if(!(i%j)) sum[i]+=j;
	for(int i=1;i<=n;++i)
		for(int j=n;j>=i;--j)
			f[j]=max(f[j],f[j-i]+sum[i]);
	printf("%d\n",f[n]);
	// for(int i=1;i<=n;++i) cout<<f[i]<<" ";cout<<endl;
	return 0;
}

top

T25 [USACO07DEC]Charm Bracelet S

传送门

水题?
啊不,这题是让我们读题。。。
抱歉我嘤语不好

#include<bits/stdc++.h>
using namespace std;
#define in Read()
int in{
	int i=0,f=1;char ch=0;
	while(!isdigit(ch)&&ch!='-') ch=getchar();
	if(ch=='-') ch=getchar(),f=-1;
	while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int N=1e5+5;
int n,v[N],m,w[N],f[N];

int main(){
	n=in,m=in;
	for(int i=1;i<=n;++i) v[i]=in,w[i]=in;
	for(int i=1;i<=n;++i)
		for(int j=m;j>=v[i];--j)
			f[j]=max(f[j],f[j-v[i]]+w[i]);
	printf("%d\n",f[m]);
	return 0;
}

top

T26 [USACO09OCT]Bessie’s Weight Problem G

传送门
又是水题

#include<bits/stdc++.h>
using namespace std;
#define in Read()
int in{
	int i=0,f=1;char ch=0;
	while(!isdigit(ch)&&ch!='-') ch=getchar();
	if(ch=='-') ch=getchar(),f=-1;
	while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int N=1e5+5;
int f[N],n,w[N],lim;

int main(){
	lim=in,n=in;
	for(int i=1;i<=n;++i)
		w[i]=in;
	for(int i=1;i<=n;++i)
		for(int j=lim;j>=w[i];--j)
			f[j]=max(f[j],f[j-w[i]]+w[i]);
	printf("%d\n",f[lim]);
	return 0;
}

top

T27 传纸条

传送门

好题!
CCF出品,必属精品!

考虑转化题意
从起点到终点只能往右或往下
求路径上值之和最大

考虑记录状态
棋盘型DP
考虑记录坐标f[x1][y1][x2][y2]

考虑转移
f[x1][y1][x2][y2]=max(f[x1][y1-1][x2][y2-1],f[x1][y1-1][x2-1][y2],f[x1-1][y1][x2][y2-1],f[x1-1][y1][x2-1][y2])+a[x1][y1]+a[x2][y2]
即四种走下来的方法取max
考虑保证路径不重合
发现步数一致(当然是一致的
考虑曼哈顿距离
显然每个状态都在一个棋盘的上三角形(还是等腰的)的斜边边界上
显然要选两个点
而且下面一个点确定了上面的点就只能在斜边边界上往上找了

考虑优化
上面已经提到两个点的相对位置
考虑枚举下面的点,利用这个相对位置算出上面的点

#include<bits/stdc++.h>
using namespace std;
#define in Read()
int in{
	int i=0,f=1;char ch=0;
	while(!isdigit(ch)&&ch!='-') ch=getchar();
	if(ch=='-') ch=getchar(),f=-1;
	while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int N=100;
int a[N][N],n,m,f[N][N][N];

int main(){
	n=in,m=in;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			a[i][j]=in;
	for(int step=3;step<=n+m;++step)
		for(int x1=1,y1=step-x1;x1<=n;++x1,--y1){
			if(y1>m) continue;
			for(int x2=x1+1,y2=step-x2;x2<=n;++x2,--y2){
				if(y2>m) continue;
				f[step][x1][x2]=max(max(f[step-1][x1][x2],f[step-1][x1-1][x2-1]),max(f[step-1][x1-1][x2],f[step-1][x1][x2-1]));
				f[step][x1][x2]+=a[x1][y1]+a[x2][y2];
			}
		}
	printf("%d\n",f[n+m-1][n-1][n]);
	// for(int k=1;k<=m+n;++k)
	// 	for(int i=1;i<=n;++i)
	// 		for(int j=1;j<=n;++j)
	// 			printf("%d(%d,%d,%d) ",f[k][i][j],k,i,j);
	return 0;
}

hurray!基本上是自己做出来的

TOP

T28 方格取数

传送门

棋盘型DP
和传纸条一样,发现每一步都有一个层次:
上三角形的斜边,只有斜边
没有前效性,DP妥妥的

#include<bits/stdc++.h>
using namespace std;
#define in Read()
int in{
	int i=0,f=1;char ch=0;
	while(!isdigit(ch)&&ch!='-') ch=getchar();
	if(ch=='-') ch=getchar(),f=-1;
	while(isdigit(ch)) i=(i<<1)+(i<<3)+ch-48,ch=getchar();
	return i*f;
}

const int N=50;
int a[N][N],n,m,f[N][N][N];

int main(){
	n=in;
	int x,y,w;
	while(x=in){
		y=in,w=in;
		a[x][y]=w;
	}
	for(int step=2;step<=n+n;++step){
		for(int x1=1,y1=step-x1;x1<=n&&y1>=1;++x1,--y1){
			for (int x2=1, y2=step-x2;x2<=n&&y2>=1;++x2, --y2) {
				f[step][x1][x2]=max(max(f[step-1][x1][x2], f[step-1][x1-1][x2-1]), max(f[step-1][x1-1][x2], f[step-1][x1][x2-1]));
				f[step][x1][x2]+=(x1==x2&&y1==y2)?a[x1][y1]:(a[x1][y1]+a[x2][y2]);
			}
		}
	}
	printf("%d\n",f[n+n][n][n]);
	// for(int k=1;k<=m+n;++k)
	// 	for(int i=1;i<=n;++i)
	// 		for(int j=1;j<=n;++j)
	// 			printf("%d(%d,%d,%d) ",f[k][i][j],k,i,j);
	return 0;
}

TOP

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值