专题讲座2 动态规划 学习心得

这回是真纯纯坐牢了

题目地址:

目录

DAY 7.13

板子例题:

1.区间DP

P4767 邮局

2.状压DP

P1433 - 吃奶酪

P1879 - Corn Fields G

P1896 [SCOI2005] 互不侵犯

输入格式

输出格式

输入输出样例

3.数位DP

P2657 - windy 数


ZJNU—动态规划

DAY 7.13

dp题目做的真的很自闭,看了半天,想了半天,绞尽脑汁,干的事情也只是懂了别人写的代码。自己写换一道一样类型(累心)的题目又不会了

吸取上次没板子的教训,离线存了点例题。但dp真搞不懂,弄不成板子(也许是懒)。

目前感觉只有数位DP找到了点套路,其他两个都太灵活了。

(基本都是洛谷的题【蓝紫相间太恐怖了】)

板子例题:

1.区间DP

感觉是最难的一个,不是很懂四边形优化后的新加的决策数组怎么处理。

P4767 邮局

高速公路旁边有一些村庄。高速公路表示为整数轴,每个村庄的位置用单个整数坐标标识。没有两个在同样地方的村庄。两个位置之间的距离是其整数坐标差的绝对值。

邮局将建在一些,但不一定是所有的村庄中。为了建立邮局,应选择他们建造的位置,使每个村庄与其最近的邮局之间的距离总和最小。

你要编写一个程序,已知村庄的位置和邮局的数量,计算每个村庄和最近的邮局之间所有距离的最小可能的总和。

Input

第一行包含两个整数:第一个是村庄 VV 的数量,第二个是邮局的数量 PP。

第二行包含 VV 个整数。这些整数是村庄的位置。

#include <bits/stdc++.h>
//#define int long long
#define CIO std::ios::sync_with_stdio(false)
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define nep(i, r, l) for (int i = r; i >= l; i--)
#define pii pair<int,int>
using namespace std;
const int N=3e3+5;
int a[N];
int dp[N][305],w[N][N];
int m[N][N];
//dp表示1~i中有j个邮局时候的距离最小值
//w表示i~j中有1个邮局时候的距离最小值 
//m是四边形优化 
int v,p;
void pre(){
	rep(l,1,v){
		w[l][l]=0;
		rep(r,l+1,v){
			w[l][r]=w[l][r-1]+a[r]-a[(l+r)>>1];
		}
	}
}
void work(){
	cin>>v>>p;
	rep(i,1,v)
		cin>>a[i];
	sort(a+1,a+v+1);
	memset(dp,0x3f,sizeof dp);
	pre();
	dp[0][0]=0;
	for (int j=1;j<=p;j++){
		m[v+1][j]=v;
		for (int i=v;i>=1;i--){
			for (int k=m[i][j-1];k<=m[i+1][j];k++){
				if (dp[k][j-1]+w[k+1][i]<dp[i][j]){
					dp[i][j]=dp[k][j-1]+w[k+1][i];
					m[i][j]=k;
				}
			}
		}
	}
	cout<<dp[v][p]<<endl;
}
signed main(){
	CIO;
	work();
	return 0;
}

2.状压DP

 感觉状压数组的第二维度一般可以用作01状态表示。

P1433 - 吃奶酪

房间里放着 nn 块奶酪。一只小老鼠要把它们都吃掉,问至少要跑多少距离?老鼠一开始在 (0,0)(0,0) 点处。

#include <bits/stdc++.h>
//#define int long long
#define CIO std::ios::sync_with_stdio(false)
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define nep(i, r, l) for (int i = r; i >= l; i--)
#define pii pair<int,int>
using namespace std;
const int N=2e5+5;
struct nl{
	double x,y;
}f[20];
double a[20][20]; 
double dis(double x1,double y1,double x2,double y2){
	return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
double dp[20][(1<<15)+2000];
void work(){
	int n;cin>>n;
	rep(i,1,n)
		cin>>f[i].x>>f[i].y;
	memset(dp,127,sizeof dp);
	rep(i,0,n){
		rep(j,i+1,n){
			a[i][j]=dis(f[i].x,f[i].y,f[j].x,f[j].y);
			a[j][i]=a[i][j];
		}
	}
	rep(i,1,n){
		dp[i][1<<(i-1)]=a[0][i];
	}
	rep(k,1,(1<<15)-1){
		rep(i,1,n){
			if ((k&(1<<(i-1)))==0) continue;
			rep(j,1,n){
				if (i==j) continue;
				if ((k&(1<<(j-1)))==0) continue;
				dp[i][k]=min(dp[i][k],dp[j][k-(1<<(i-1))]+a[i][j]);
			}
		}
	}
	double mi=INT_MAX;
	rep(i,1,n){
		mi=min(mi,dp[i][(1<<n)-1]);
	}
	printf("%.2lf",mi);
}
signed main(){
	CIO;
	work();
	return 0;
}

P1879 - Corn Fields G

农场主John新买了一块长方形的新牧场,这块牧场被划分成M行N列(1 ≤ M ≤ 12; 1 ≤ N ≤ 12),每一格都是一块正方形的土地。John打算在牧场上的某几格里种上美味的草,供他的奶牛们享用。

遗憾的是,有些土地相当贫瘠,不能用来种草。并且,奶牛们喜欢独占一块草地的感觉,于是John不会选择两块相邻的土地,也就是说,没有哪两块草地有公共边。

John想知道,如果不考虑草地的总块数,那么,一共有多少种种植方案可供他选择?(当然,把新牧场完全荒废也是一种方案)

Input

第一行:两个整数M和N,用空格隔开。

第2到第M+1行:每行包含N个用空格隔开的整数,描述了每块土地的状态。第i+1行描述了第i行的土地,所有整数均为0或1,是1的话,表示这块土地足够肥沃,0则表示这块土地不适合种草。

#include <bits/stdc++.h>
#define int long long
#define CIO std::ios::sync_with_stdio(false)
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define nep(i, r, l) for (int i = r; i >= l; i--)
#define pii pair<int,int>
using namespace std;
const int N=13;
const int mod=1e9;
int f[N][N];
int fs[N];
bool st[1<<N];
int dp[N][(1<<N)+10];
void work(){
	int n,m;
	cin>>n>>m;
	rep(i,1,n){
		rep(j,1,m){
			cin>>f[i][j]; 
			fs[i]=(fs[i]<<1)+f[i][j];
		}
	}
	
	rep(i,0,(1<<m)-1)
		st[i]=((i&(i<<1))==0)&&((i&(i>>1))==0);
	
	dp[0][0]=1;
	rep(i,1,n){
		rep(j,0,(1<<m)-1){//i行状态 
			if (st[j]!=1) continue;//左右不能重复 
			if ((fs[i]&j)!=j) continue;//这个暴力出来的j一定要是第i行的子集 
			rep(k,0,(1<<m)-1){//i-1行状态 
				if ((j&k)!=0) continue;//上下不能重复 
				dp[i][j]+=dp[i-1][k];
				dp[i][j]%=mod; 
			}
		}
	}
	int ans=0;
	rep(i,0,(1<<m)-1){
		ans+=dp[n][i];
		ans%=mod;
	}
	cout<<ans<<endl;
}
signed main(){
	CIO;
	work();
	return 0;
}

P1896 [SCOI2005] 互不侵犯

在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。

注:数据有加强(2018/4/25)

输入格式

只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)

输出格式

所得的方案数

输入输出样例

输入 #1复制

3 2

输出 #1复制

16
#include <bits/stdc++.h>
#define int long long
#define CIO std::ios::sync_with_stdio(false)
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define nep(i, r, l) for (int i = r; i >= l; i--)
#define pii pair<int,int>
using namespace std;
const int N=11;
int num[1<<N],pre[1<<N];
int dp[N][N*N][1<<N];
void work(){
	int n,k,cnt=0;
	cin>>n>>k;
	rep(i,0,(1<<n)-1){
		if (((i>>1)&i)!=0||((i<<1)&i)!=0)continue;
		int I=i,zhi=0;
		while(I){
			if (I&1) zhi++;
			I=I/2; 
		}
		num[i]=zhi;
		pre[++cnt]=i;
	}
//	for (int i=1;i<=9;i++){
//    	cout<<num[i]<<" "<<pre[i]<<endl;
//	}
	dp[0][0][0]=1;
	rep(i,1,n){
		rep(l,1,cnt){
			rep(r,1,cnt){
				int s1=pre[l],s2=pre[r];
				if ((s1&(s2>>1))!=0||(s1&(s2<<1))!=0||(s1&s2)!=0) continue;
				rep(kk,0,k){
					if (kk-num[s1]>=0){
						dp[i][kk][s1]+=dp[i-1][kk-num[s1]][s2];
					}
				}
			}
		}
	}
	int ans=0;
	rep(i,1,cnt) ans+=dp[n][k][pre[i]];
	cout<<ans<<endl;
}
signed main(){
	CIO;
	work();
	return 0;
}

3.数位DP

dp用在开头预存dp数组,然后前缀和输出,中间维护一下work每种情况(做了两道都是这样)

还有记忆化搜索的套路

P2657 - windy 数

不含前导零且相邻两个数字之差至少为 22 的正整数被称为 windy 数。windy 想知道,在 aa 和 bb 之间,包括 aa 和 bb ,总共有多少个 windy 数?

#include <bits/stdc++.h>
#define int long long
#define CIO std::ios::sync_with_stdio(false)
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define nep(i, r, l) for (int i = r; i >= l; i--)
#define pii pair<int,int>
using namespace std;
const int N=13;
const int mod=1e9;
int dp[N][N];
int a[N];
//i表示多少数位,最左边的数是j 
int pre(){
	rep(i,0,9){
		dp[1][i]=1;
	}
	rep(i,2,10){
		rep(j,0,9){
			rep(k,0,9){
				if (abs(j-k)>=2){
					dp[i][j]+=dp[i-1][k];
				}
			}
		}
	}
}
int numdp(int x){
	memset(a,0,sizeof a);
	int ans=0,len=0;
	while (x>0){
		a[++len]=x%10;
		x=x/10;
	//	cout<<a[len]<<endl;
	}
	rep(i,1,len-1){
		rep(j,1,9){
			ans+=dp[i][j];
		}
	}
	rep(i,1,a[len]-1){
		ans+=dp[len][i];
	}
	//cout<<ans<<"!!"<<endl;
	nep(i,len-1,1){
		rep(j,0,a[i]-1){
			if (abs(j-a[i+1])>=2){
				ans+=dp[i][j];
			}
		}
		if (abs(a[i]-a[i+1])<2){
			break;
		}
	}
	return ans;
}
void work(){
	pre();
	int a,b;
	cin>>a>>b;
	cout<<numdp(b+1)-numdp(a)<<endl;
}
signed main(){
	CIO;
	work();
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

繁水682

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值