线性动态规划(洛谷)

ACM题集:https://blog.csdn.net/weixin_39778570/article/details/83187443
动态规划:https://blog.csdn.net/weixin_39778570/article/details/87014343

P1020 导弹拦截

LIS
f[i] 表示 a[i] 结尾的最长上升子序列
0<=j<i,a[j]<a[i],====> f[i] = max(f[i],f[j]+1), 时间复杂度O(n^2)
现在数据量比较大,以下为O(nlogn)做法
#include<bits/stdc++.h>
#define ll long long 
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
int n=0;
int a[100005],b[100005],c[100005];
// 找到第一个小于x的下标 
int my_lower_find(int *a, int l, int r, int x){
	int ans = r,mid;
	while(l<=r){
		mid = (l+r)>>1;
		if(x>a[mid]){
			ans = mid;
			r = mid-1; // 递减序列,往左继续查找是否有小于x的 
		}else l = mid + 1;
	}	
	return ans;
}
// 找到第一个大于等于x的下标 , 严格递增序列 
int my_upper_find(int *a, int l, int r, int x){
	int ans = l,mid;
	while(l<=r){
		mid = (l+r)>>1;
		if(a[mid]>=x){
			ans = mid;
			r = mid - 1; // 递增序列,往左找更下的大于等于x的	
		}else l = mid+1;
	}
	return ans;
}
void solve(){
	int len1=1,len2=1;
	b[1]=c[1]=a[1];
	fo(i,2,n){
		// 最长下降子序列 
		if(a[i]<=b[len1])b[++len1] = a[i];
		else{
			int t = my_lower_find(b,1,len1,a[i]);
			b[t] = a[i];
		}
		// 最长严格上升子序列 
		if(a[i]>c[len2])c[++len2] = a[i];
		else{
			int t = my_upper_find(c,1,len2,a[i]);
			c[t] = a[i];
		}
	}
	printf("%d\n%d\n",len1,len2); 
}
int main(){
	while(scanf("%d",&a[++n])!=EOF);
	n--;
	solve();
	return 0;
} 

P1091 合唱队形

单调增+单调减, 找最长的长度
#include<bits/stdc++.h>
#define ll long long 
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
int n, a[105],b[105],c[105];
// 找到第一个大于等于x的下标 , 严格递增序列 
int my_upper_find(int *a, int l, int r, int x){
	int ans = l, mid;
	while(l<=r){
		mid = (l+r)>>1;
		if(a[mid]>=x){
			ans = mid;
			r = mid - 1;
		}else l = mid+1;
	}	
	return ans;
}
void solve(){
	int ans = 0, len1=1, len2 = 1;
	fo(k,1,n){ // 枚举分割点,不能越界 
		len1 = 1, len2 = 1;
		b[1]=a[1], c[1]=a[n];
		// 最长严格上升 
		fo(i,2,k){
			if(a[i]>b[len1])b[++len1]=a[i];
			else{
				int t = my_upper_find(b,1,len1,a[i]);
				b[t] = a[i];
			}
		}
		// 最长严格下降,即反过来的最长严格上升 
		for(int i=n-1; i>=k; i--){
			if(a[i]>c[len2]) c[++len2]=a[i];
			else{
				int t = my_upper_find(c,1,len2,a[i]);
				c[t] = a[i];
			}
		} 
		ans = max(ans, b[len1]==c[len2]?len1+len2-1:len1+len2); // 枚举k,但可能两段序列都选中了a[k] 
	}
	cout<<n-ans;
} 
int main(){
	scanf("%d",&n);
	fo(i,1,n)scanf("%d",&a[i]);
	solve();
	return 0;
}

P1280 尼克的任务

在时间1-N内,给定m个时间段,同一个时刻只能有一个时间段,某时刻有任务必须选一个,求最大空闲时间
---------------------------------------------------------------------------------------
这道题的主要解决是的同一时刻有多个任务可以执行要选择那一个的问题
如果顺推的话,我们选择了某个任务,但是我们不知道选择该任务之后的最大休息时间,所以顺推需要dfs
那我们考虑逆推,f[i] 表示从i到n的最大休息时间,那么显然f[n+1]=0
当i时刻没有任务时 f[i] = f[i+1]+1
当i时刻有任务时   f[i] = max(f[i], f[i+a[num].t]) a[num].t表示第num个任务的持续时间
所以我们只要枚举相同起始时间的任务的选择,逆推,结果就是f[1] 1~n的最大休息时间	
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
struct node{
	int st,t;
	bool operator < (const node &b)const{
		return st>b.st; // 按左端点从大到小排序 
	}
}a[10005];
int n,m,sum[10005]; // sum[i] 表示i时刻的任务数 
int f[10005];
void solve(){
	sort(a+1,a+1+m);
	int num= 1; // 当前选择的任务是第num个 
	for(int i=n; i>=1; i--){ // f[i] 表示 从i开始到n的最大休息时间,最优子结构是f[n+1]=0 
		if(sum[i]==0)f[i] = f[i+1]+1; // i时刻无任务 
		else{
			for(int j=1; j<=sum[i]; j++){ // 相同开始时间的选择 
				f[i] = max(f[i], f[i+a[num].t]);  // f[i]一开始为0,所以一定会选一个 
				num++;// 任务+1					  // 之后就是在相同起始时间的任务中进行挑选 
			}								    
		}
	}
	cout<<f[1]; 
}
int main(){
	cin>>n>>m;
	fo(i,1,m){
		scanf("%d%d",&a[i].st,&a[i].t); // 开始时间,持续时间 
		sum[a[i].st]++;
	}	
	solve();	
	return 0;
}
标准库multimap可以储存一对多的键值对,
适合这里的一个开始时间可能对应不同任务。
可以利用上述方式来遍历一个键对应的值。 
dfs(k)的意义是,保证了k时刻在做选择之前是自由时刻,
k时刻可能有几个任务可以选,也可能没有任务,分别处理之。
return时做一下记忆化即可。
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
multimap<int,int> mp; // 标准库multimap可以储存一对多的键值对
int n,m,f[10005];
int dfs(int t){ // t...n的最小工作时间 
	if(f[t])return f[t];
	if(t>n)return 0;
	int ans = 999999999;
	for(auto it = mp.lower_bound(t); it!=mp.upper_bound(t); it++){ // 一键多值遍历 
		ans = min(dfs(it->second)+ it->second - it->first, ans); // 枚举相同起始时间的任务,找到最小的工作时间 
	}
	if(ans == 999999999) ans = dfs(t+1); // 没任务
	return f[t] = ans; 
}
int main(){
	cin>>n>>m;
	int s,t;
	fo(i,1,m){
		scanf("%d%d",&s,&t);
		mp.insert({s,s+t});	// 起始时间,和下一个起始时间 
	}
	cout<<n-dfs(1);
	return 0;
}

P1880 [NOI1995]石子合并

区间dp 环形,注意枚举起点一定要枚举全部
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
int f1[205][205],f2[205][205],a[205],sum[205],n;
int d(int i, int j){return sum[j]-sum[i-1];}
void solve(){
	fo(k,2,n){ // 枚举区间长度 
		for(int i=1; i+k-1<2*n; i++){ // 枚举起点 
			int j = i+k-1;
			f1[i][j]=999999999;
			fo(t,i,j-1){ // t一定要比右边界小1,不然f2[i][t]就等于f2[i][j]了,枚举了两倍,答案翻倍 
				f1[i][j] = min(f1[i][j], f1[i][t]+f1[t+1][j]+d(i,j));
				f2[i][j] = max(f2[i][j], f2[i][t]+f2[t+1][j]+d(i,j)); // t=j时,f[i][j]+d(i,j) 算了两倍,t=i时是初始化 
			}
		}
	}
	int ans1 = 99999999, ans2=0;
	fo(i,1,n){
		ans1 = min(ans1, f1[i][i+n-1]);
		ans2 = max(ans2, f2[i][i+n-1]);
	}
	printf("%d\n%d\n",ans1,ans2);
}
int main(){
	cin>>n;
	fo(i,1,n)scanf("%d",&a[i]),a[i+n]=a[i];
	fo(i,1,2*n)sum[i] = sum[i-1]+a[i];
	solve();
	return 0;
}

P1140 相似基因

dij 表示s1的前i个s2的前j个字符串的最大匹配度
显然对于每个s1[i],s2[j]只有三中匹配状态,如下: 
int t1 = dp[i-1][j-1]+mp[s1[i]][s2[j]];
int t2 = dp[i-1][j]+mp[s1[i]]['-'];
int t3 = dp[i][j-1]+mp['-'][s2[j]];
dp[i][j] = max(t1,max(t2,t3));
#include<bits/stdc++.h>
#define ll long long 
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
int mp[256][256];
void init(){
	mp['A']['A']=5,mp['A']['C']=-1,mp['A']['G']=-2,mp['A']['T']=-1,mp['A']['-']=-3;
	mp['C']['A']=-1,mp['C']['C']=5,mp['C']['G']=-3,mp['C']['T']=-2,mp['C']['-']=-4;
	mp['G']['A']=-2,mp['G']['C']=-3,mp['G']['G']=5,mp['G']['T']=-2,mp['G']['-']=-2;
	mp['T']['A']=-1,mp['T']['C']=-2,mp['T']['G']=-2,mp['T']['T']=5,mp['T']['-']=-1;
	mp['-']['A']=-3,mp['-']['C']=-4,mp['-']['G']=-2,mp['-']['T']=-1,mp['-']['-']=0;
}	
int n,m,dp[105][105];
char s1[105],s2[105];
void solve(){
	fo(i,1,n)dp[i][0] = dp[i-1][0]+mp[s1[i]]['-'];
	fo(j,1,m)dp[0][j] = dp[0][j-1]+mp['-'][s2[j]];
	fo(i,1,n){
		fo(j,1,m){
			int t1 = dp[i-1][j-1]+mp[s1[i]][s2[j]];
			int t2 = dp[i-1][j]+mp[s1[i]]['-'];
			int t3 = dp[i][j-1]+mp['-'][s2[j]];
			dp[i][j] = max(t1,max(t2,t3));		
		} 
	} 
	cout<<dp[n][m];
}
int main(){
	init();
	scanf("%d%s",&n,s1+1);
	scanf("%d%s",&m,s2+1);
	solve();
	return 0;
}

P1282 多米诺骨牌

给定数组a,b,第i个位置的a[i]和b[i]可以交换,问|sum(a)-sum(b)|最小时最小的交换次数为多少
------------------------------------------------------------------------------------
dij  表示前i个和上面和为j的最小翻转次数
可以看做一个01背包问题
只不过到了第i个商品是不是选择与不选择
而是选上或者选下
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
const int INF = 1e9;
int n,a[1005],b[1005],dp[1001][6001],sum;
void solve(){
	fo(i,1,n)fo(j,0,6*n)dp[i][j] = INF;
	// dij 表示第i个骨牌,和为j的最小翻牌数 
	dp[1][b[1]]=1,dp[1][a[1]]=0; // 不能颠倒位置,因为有3,3这种情况
	fo(i,2,n){ // 阶段 
		fo(j,0,6*n){ // 状态 
			if(j>=a[i]) dp[i][j] = min(dp[i][j], dp[i-1][j-a[i]]); // 选上 
			if(j>=b[i]) dp[i][j] = min(dp[i][j], dp[i-1][j-b[i]]+1); // 选下 
		}
	}
	int MIN1 = INF, MIN2=INF;
	fo(j,0,6*n){
		if(dp[n][j] != INF)
		if(fabs((sum-j-j)) < MIN1){
			MIN1 = fabs(sum-j-j);
			MIN2 = dp[n][j];
		}else if(fabs((sum-j-j)) == MIN1) MIN2 = min(MIN2,dp[n][j]);
	}
	cout << MIN2;
}
int main(){
	cin>>n;
	fo(i,1,n)scanf("%d%d",&a[i],&b[i]),sum+=a[i]+b[i];
	solve();
	return 0;
}
已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页