背包的就不贴上来了 感觉没啥好说的
AcWing 898. 数字三角形
线性dp 第一题 应该都是一眼秒吧 感觉自己第一遍学的时候是真的菜 呃呃
我们注意边界情况就可以了 状态转移没啥好说的 下一道
#include <bits/stdc++.h>
using namespace std;
const int N = 510;
const int M = 1e4+10;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int max(int x,int y){return x>y?x:y;}
int min(int x,int y){return x<y?x:y;}
int a[N][N],f[N][N];
signed main(){
fast
int n;cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
cin>>a[i][j];
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
if(j==1)f[i][j]=f[i-1][j]+a[i][j];
else if(j==i)f[i][j]=f[i-1][j-1]+a[i][j];
else f[i][j]=max(f[i-1][j-1],f[i-1][j])+a[i][j];
}
}
int ans=-2e9;
for(int i=1;i<=n;i++)ans=max(ans,f[n][i]);
cout<<ans<<endl;
return 0^0;
}
895. 最长上升子序列
其实这道题也差不多能一眼秒的 直接暴力n^2就过了
状态表示:f[i]表示为末尾为第i位的max
然后我们就可以枚举每个i前面的数字
if (w[j] < w[i])f[i] = max(f[i], f[j] + 1);
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3+10;
const int M = 1e4+10;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int max(int x,int y){return x>y?x:y;}
int min(int x,int y){return x<y?x:y;}
int n,f[N],w[N];
signed main(){
fast
cin>>n;
for(int i=1;i<=n;i++)cin>>w[i];
for(int i=0;i<N;i++)f[i]=1;
int ans=-2e9;
for(int i=1;i<=n;i++) {
for (int j = 1; j <= i; j++) {
if (w[j] < w[i])f[i] = max(f[i], f[j] + 1);
ans=max(ans,f[i]);
}
}
cout<<ans<<endl;
return 0^0;
}
896. 最长上升子序列 II
到这里才感觉有点难度 可是我还是记得到 做法是二分
最开始没想清楚在哪二分 然后去洗了个澡就想出来了 (这其实也告诉我们一个道理 一道题不能死磕 说不定过几天 就能想出来了
这次我们状态表示就不再是simple版那样了
我们把f[i]表示为当子序列为i位时最小的序号是谁 我们不难看出这个其实有单调性啊
要是我们后面的数字序号很小 那么就可以和前面的替换之 要是很大 那我们就可以在后面补上
反正都是有地方可以去的
代码甚至比simple 还短
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
const int M = 1e4+10;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int max(int x,int y){return x>y?x:y;}
int min(int x,int y){return x<y?x:y;}
int n,w[N];
signed main(){
fast
cin>>n;
for(int i=1;i<=n;i++)cin>>w[i];
vector<int>f;
for(int i=1;i<=n;i++){
if(lower_bound(f.begin(),f.end(),w[i])==f.end())f.push_back(w[i]);
else f[lower_bound(f.begin(),f.end(),w[i])-f.begin()]=w[i];
}
cout<<f.size()<<endl;
return 0^0;
}
897. 最长公共子序列
我们不难想到把f_i_j 表示为 a_i 和 b_j 能凑出的子串max
状态方程 f[i][j]=max(f[i-1][j],f[i][j-1]); 当然要是a_i==b_j?f[i][j]=max(f[i][j],f[i-1][j-1]):max(f[i][j],f[i-1][j-1]+1)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3+10;
const int M = 1e4+10;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int max(int x,int y){return x>y?x:y;}
int min(int x,int y){return x<y?x:y;}
int f[N][N];
signed main(){
fast
int n,m;cin>>n>>m;
char a[N],b[N];
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=m;i++)cin>>b[i];
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(a[i]==b[j])f[i][j]=max(f[i-1][j-1]+1,max(f[i-1][j],f[i][j-1]));
else f[i][j]=max(f[i-1][j],f[i][j-1]);
}
}
cout<<f[n][m]<<endl;
return 0^0;
}
AcWing 902. 最短编辑距离
这个很清楚的二维表示 状态方程的三种情况也写得很明白
f_i_j 表示 a_i 变成 b_j 的min
状态计算 :从最后一步考虑
要是我们f_i_j是要进行一步删除操作才能匹配 自然 f_i-1_j +1 (这个有点绕 可能会整反
增加自然就是 f_i_j-1 +1
改的话自然两种情况 a[i]==b[j]?f[i][j]=min(f[i][j],f[i-1][j-1]):f[i][j]=min(f[i][j],f[i-1][j-1]+1);
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3+10;
const int M = 1e4+10;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int max(int x,int y){return x>y?x:y;}
int min(int x,int y){return x<y?x:y;}
int f[N][N];
signed main(){
fast
int n,m;
char a[N];cin>>n>>a+1;
char b[N];cin>>m>>b+1;
for(int i=1;i<=n;i++)f[i][0]=i;
for(int i=1;i<=m;i++)f[0][i]=i;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1);
a[i]==b[j]?f[i][j]=min(f[i][j],f[i-1][j-1]):f[i][j]=min(f[i][j],f[i-1][j-1]+1);
}
}
cout<<f[n][m];
return 0^0;
}
AcWing 899. 编辑距离
和刚刚一模一样 只是处理了一下输入
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3+10;
const int M = 1e4+10;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int max(int x,int y){return x>y?x:y;}
int min(int x,int y){return x<y?x:y;}
char s[N][15];
int f[N][N];
signed main(){
int n,m;
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)scanf("%s",s[i]+1);
while(m--){
char a[N];int x,ans=0;scanf("%s%lld",a+1,&x);
for(int k=1;k<=n;k++) {
int cnt1=strlen(a+1),cnt2= strlen(s[k]+1);
for(int i=1;i<=cnt1;i++)f[i][0]=i;
for(int i=1;i<=cnt2;i++)f[0][i]=i;
for (int i = 1; i<=cnt1; i++) {
for (int j = 1;j<=cnt2; j++) {
f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1);
if(a[i]==s[k][j])f[i][j]=min(f[i][j],f[i-1][j-1]);
else f[i][j]=min(f[i][j],f[i-1][j-1]+1);
}
}
if(x>=f[cnt1][cnt2])ans++;
}
cout<<ans<<endl;
}
return 0^0;
}
AcWing 282. 石子合并
区间dp
有点分治的意思?所以我们这题用了记忆化搜索来做了一下
当然也可以倒着推 也可以枚举长度
状态表示:f_i_j 表示 [i,j] 区间构成的min
状态方程:f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]) 后面就是前缀和
dp版本 运行时间: 102 ms
#include <bits/stdc++.h>
using namespace std;
const int N = 310;
int f[N][N],s[N];
signed main(){
int n;cin>>n;
for(int i=1;i<=n;i++)cin>>s[i],s[i]+=s[i-1];
memset(f,0x3f,sizeof f);
for(int i=n;i>=1;i--)
for(int j=i;j<=n;j++)
for(int k=i;k<=j;k++)
i==j?f[i][j]=0:f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]);
cout<<f[1][n]<<endl;
return 0^0;
}
记忆化搜索版本 运行时间: 151 ms
#include <bits/stdc++.h>
using namespace std;
const int N = 310;
const int M = 1e4+10;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int max(int x,int y){return x>y?x:y;}
int min(int x,int y){return x<y?x:y;}
int a[N],s[N],f[N][N];
int dp(int l,int r){
int &v=f[l][r];
if(v!=-1)return v;
v=1e9;
for(int k=l;k<=r;k++)l==r?f[l][r]=0:f[l][r]=min(f[l][r],dp(l,k)+dp(k+1,r)+s[r]-s[l-1]);
return v;
}
signed main(){
fast
int n;cin>>n;
for(int i=1;i<=n;i++)cin>>a[i],s[i]=s[i-1]+a[i];
memset(f,-1,sizeof f);
cout<<dp(1,n);
return 0^0;
}
AcWing 900. 整数划分
计数类dp
我们可以将n拆成有n个权重的物品 我们要装满体积为n的背包 可以用无数次 求cnt
状态表示:f_i_j 前i种物品装满体积为j的背包的cnt
状态计算:f[i][j]=f[i-1][j]+f[i-1][j-i]+f[i-1][j-2i]+......
后面可以用f[i][j-i]来表示 完全背包推过
所以状态方程就是:f[i][j]=(f[i-1][j]+f[i][j-i])
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3+10;
const int M = 1e4+10;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int max(int x,int y){return x>y?x:y;}
int min(int x,int y){return x<y?x:y;}
int f[N][N];
signed main(){
fast
int n;cin>>n;
for(int i=1;i<=n;i++)f[i][0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
f[i][j]=f[i-1][j];
if(j>=i)f[i][j]=(f[i-1][j]+f[i][j-i])%mod;
}
}
cout<<f[n][n]<<endl;
return 0^0;
}
AcWing 291. 蒙德里安的梦想
第一次学的dp的时候跳了状态压缩
最近看群友用这个秒天秒地 不仅心动起来 才发现这个其实也没那么难
但是使用数据是真的小啊
首先我们有一个推论:
这道题我们合法的横向小方块摆放的位置的方案数就是总的方案数
这就是个乘法原理 仔细想一下就可以明白
然后我们就来推状态表示
这个状态表示很逆天啊 确实第一次做 肯定时想不出来的
f_i_j 表示的是 将前i-1 列已经固定死 然后 从i-1 列 伸到 i列的方块状态为j的 cnt (很绕
然后状态计算就是
f_i_j += f_i-1_k
but k 要合法才行
什么时候不合法?
当 k&j!=0 时 就证明其方框一定有重叠 不合法
还有就是k|j 他的中间0的个数不能是偶数个
我们可以算一下其时间复杂度11个数据2的11次方是2000+
O(2000*2000*11*T)好像可以勉强卡过
但是我们也可以预处理出st【】来判断这个是不是合法的
还可以更优化一点 直接把这个数和另一个数组合合法的直接放进去 到时候直接取出来用就是了
运行时间: 196 ms
结果: Accepted
#include <bits/stdc++.h>
using namespace std;
const int N = 12;
const int M = 1<<12;
const int mod = 1e9+7;
#define int long long
#define endl '\n'
#define Endl '\n'
#define inf 0x3f3f3f3f3f3f3f3f
#define fast ios::sync_with_stdio(false);cin.tie(nullptr);
int max(int x,int y){return x>y?x:y;}
int min(int x,int y){return x<y?x:y;}
int n,m,f[N][M];
bool st[M];
vector<int>state[M];
signed main(){
fast
while(cin>>n>>m,n||m){
for(int i=0;i<1<<n;i++){
int cnt=0;st[i]=1;
for(int j=0;j<n;j++){
if(i>>j&1){
if(cnt%2){st[i]=false;break;}
}else cnt++;
}
if(cnt%2)st[i]=false;
}
for(int i=0;i<1<<n;i++) {
state[i].clear();
for (int j = 0; j < 1 << n; j++)
if ((i & j) == 0 && st[i | j])
state[i].push_back(j);
}
memset(f,0,sizeof f);
f[0][0]=1;
for(int i=1;i<=m;i++)
for(int j=0;j<1<<n;j++)
for(auto k:state[j])
f[i][j]+=f[i-1][k];
cout<<f[m][0]<<endl;
}
return 0^0;
}