线性动态规划的归纳总结(自己太笨了,不总结碰到又不会)

归纳模式一

从n个东西从选取m个东西,求其所得到的相应的sum的最小值,(sum与选取的m个东西的相邻方式有关)

想到的第一个思路:
令f[i][j]为有前i种物品情况下,选取j个物品的sum。但是我们发现一个很大的弊端,因为sum与第i个物品的上一个物品有关,而我们无法知道上一个物品究竟是哪个,这就很难推出状态转移方程

想到的第二个思路(正确思路)
令f[i][j]为以第i个物品结尾时,选取j的物品的sum。
易知:f[i][j]由f[k][j-1]得来,且很容易得出第i,k物品的关系
则状态转移方程:f[i][j]=min(f[k][j-1]+g(i,k))(1<=k<i)

例题1,书本整理

g(i,k)=abs(a[i]-a[k])

#include<bits/stdc++.h>
using namespace std;
struct ppp {
    
    
	int h,l;
} a[1000];
int cmp(ppp x,ppp y) {
    
    
	return x.h<y.h;
}
int n,t,f[105][105];

void init() {
    
    
	for(int i=1; i<=n; i++) {
    
    
		for(int j=1; j<=t; j++) {
    
    
			f[i][j]=1e9;//初始化成最大值 
		}
	}
	for(int i=1; i<=n; i++)f[i][1]=0;//只有一本书,自然以它结尾的sum=0 
}


int main() {
    
    
	cin>>n>>t;
	t=n-t;
	for(int i=1; i<=n; i++)cin>>a[i].h>>a[i].l;
	sort(a+1,a+n+1,cmp);//排序不用说了 
	init();

	for(int i=1; i<=n; i++) {
    
    
		for(int j=2; j<=i; j++) {
    
    
		//j=1已经处理过了,直接从2开始;
		//有i本数自然最多也只能选取i本书 
			for(int k=1; k<i; k++) {
    
    
			//第i本书的前一本书可以是从1到i-1的任意一本 
				f[i][j]=min(f[k][j-1]+abs(a[k].l-a[i].l),f[i][j]);
				//通俗易懂的状态转移方程 
			}
		}
	}
	int ans=1e9;
	for(int i=1;i<=n;i++)ans=min(ans,f[i][t]);
	//整理好的书的结尾可能是1,n的任意一本 
	cout<<ans;
	return 0;
}

归纳模式二

相邻位置选与不选问题:相邻两个点至少有一个点需要选
令f[i][1]表示第i个点选
令f[i][2]表示第i个点不选
易知状态转移如下:
一维:
f[i][1]=min(f[i-1][1],f[i-1][2])+a[i]
f[i][2]=f[i-1][1]
二维图:
f[u][1]=sum(min(f[v][1],f[v][2]))+a[u]
f[u][2]=sum(f[v][1])

例题1,板子题

不解释

归纳模式三

分配问题:将m个物品分配给n个人,其价值由分配的方式决定
设f[i][j]表示将j个物品分配给i个人所得到的总价值
设a[i][k]表示将k个物品分配给第i个人所得到的价值
则f[i-1][j-k]+a[i][k]表示将j-k个物品分配前i-1个人且第i个人分配k个物品所得到的总价值
易知状态转移方程如下:
f[i][j]=max(f[i-1][j-k]+a[i][k]),0<=k<=j

例题

#include<iostream>
#include<cmath>
using namespace std;
long long f[205][205],a[205],b[205],v[205][205];

int main(){
    
    
	int m,n;
	cin>>m>>n;
	for(int i=1;i<=n;i++)cin>>a[i]>>b[i];
	for(int i=1;i<=n;i++){
    
    
		for(int j=0;j<=m;j++){
    
    
			v[i][j]=a[i]*pow(j,b[i]);
		} 
	} 
	for(int j=1;j<=m;j++)f[0][j]=1e9;
	for(int i=1;i<=n;i++){
    
    
		for(int j=1;j<=m;j++){
    
    
			f[i][j]=1e9;
			for(int k=0;k<=j;k++){
    
    
				f[i][j]=min(f[i][j],f[i-1][j-k]+v[i][k]);
			}
			
		}
	}
	cout<<f[n][m];
	return 0;
} 

归纳模式四(线段覆盖)

经典的线段覆盖问题:一维坐标系中,已知n个线段的左右端点坐标

问题1:求不重合下的最长覆盖
设f[i]一维坐标上从 0~i,线段的最长不重合覆盖
vector存储以线段右端点为下标的左端点坐标
则有f[i]=max(f[vec[i][j]]+i-vec[i][j]),且f[i]由f[i-1]继承

例题

#include<iostream>
#include<cmath>
#include<vector>
using namespace std;

vector<int>vec[30005];
int f[30005];

int main(){
    
    
	int n,l,r;
	cin>>n;
	for(int i=1;i<=n;i++){
    
    
		cin>>l>>r;
		vec[r].push_back(l);
	}
	for(int i=0;i<=30000;i++){
    
    
		if(i>0)f[i]=f[i-1];
		for(int j=0;j<vec[i].size();j++){
    
    
			f[i]=max(f[i],f[vec[i][j]]+i-vec[i][j]);
		}
	}
	cout<<f[30000];
	return 0;
} 

归纳模式五

音量调节问题:每次调节都会使原来的音量增加或减少ai,求最后可调节到的最大音量
设 f[i][j] 为调节完 i 次之后能否调节成音量 j
易知状态转移方程:
if(f[i-1][j])f[i][j+a[i]]=f[i][j-a[i]]=1

例题

要整除k,可以看出若k=10,1与11%10的结果使一样的,其意义也是一样的,所以第二维循环k次即可

#include<iostream>
#include<cmath>
#include<vector>
#include<cstring>
using namespace std;

int f[10005][105],a[10005];

int main(){
    
    
	int T,n,k;
	cin>>T;
	while(T--){
    
    
		cin>>n>>k;
		for(int i=1;i<=n;i++)cin>>a[i];
		memset(f,0,sizeof(f));
		
		int x=(a[1]%k+k)%k;
		f[1][x]=1;
		for(int i=2;i<=n;i++){
    
    
			for(int j=0;j<k;j++){
    
    
				if(f[i-1][j]==1){
    
    
					x=((j+a[i])%k+k)%k;
					f[i][x]=1;
					x=((j-a[i])%k+k)%k; 
					f[i][x]=1;
				}
			}
		}
		if(f[n][0]==1)cout<<"Divisible"<<endl;
		else cout<<"Not divisible"<<endl; 
	}
	
	return 0;
} 

归纳模式六

例题1

例题2

猜你喜欢

转载自blog.csdn.net/weixin_43602607/article/details/109619278