归纳模式一
从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)
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])
不解释
归纳模式三
分配问题:将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;
}