2024.11.17
今日总结
上午做了几道线性Dp和stl,下午打比赛但是打了一会发现不会就去写状压Dp和状态机Dp和区间Dp了
1:传纸条
这道题就是一道的线性Dp的题目,但是他涉及到多维同时求,只需要同时走即可,从最后一步的来源推出动态转移方程,不要有贪心的思想!
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 55;
int n, m;
int g[N][N];
int f[N * 2][N][N];
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
scanf("%d", &g[i][j]);
for (int k = 2; k <= n + m; k ++ )
for (int i = max(1, k - m); i <= n && i < k; i ++ )
for (int j = max(1, k - m); j <= n && j < k; j ++ )
{
int t = g[i][k - i];
if(i != j) t += g[j][k - j];
for (int a = 0; a <= 1; a ++ )
for (int b = 0; b <= 1; b ++ )
f[k][i][j] = max(f[k][i][j],f[k - 1][i - a][j - b] + t);
}
printf("%d\n", f[n + m][n][n]);
return 0;
}
2:最长公共子序列
这是一道模板题
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n,len;
int a[N],b[N],mp[N],dp[N];
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i ++)
{
scanf("%d",&a[i]);
mp[a[i]] = i;
}
for(int i = 1;i <= n;i ++)
scanf("%d",&b[i]);
memset(dp,0x3f,sizeof(dp));
dp[0] = 0;
for(int i = 1;i <= n;i ++)
{
int l = 0,r = len;
if(mp[b[i]] > dp[len]) dp[++ len] = mp[b[i]];
else
{
while(l < r)
{
int mid = l + r >> 1;
if(dp[mid] > mp[b[i]]) r = mid;
else l = mid + 1;
}
dp[l] = min(mp[b[i]],dp[l]);
}
}
printf("%d\n",len);
return 0;
}
3:最长公共上升子序列
这道题目结合了最长上升子序列 + 最长公共子序列两者,只需要将两者同时考虑即可
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 3010;
int n;
int a[N],b[N],dp[N][N]; // dp[i][j]代表所有a[1 ~ i]和b[1 ~ j]中以b[j]结尾的公共上升子序列的集合中长度的最大值
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i ++)
scanf("%d",&a[i]);
for(int i = 1;i <= n;i ++)
scanf("%d",&b[i]);
for(int i = 1;i <= n;i ++)
{
int Max = 1;
for(int j = 1;j <= n;j ++)
{
dp[i][j] = dp[i - 1][j];
if(a[i] == b[j]) dp[i][j] = max(dp[i][j],Max);
if(a[i] > b[j]) Max = max(Max,dp[i - 1][j] + 1);
}
}
int res = 0;
for(int i = 1;i <= n;i ++)
res = max(res,dp[n][i]);
printf("%d\n",res);
return 0;
}
4:股票买卖
这道题的关键在于将自己对于股票的三种状态表示出来,用三者之间的关系来求解动态转移方程即可
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
const int INF = 0x3f3f3f3f;
int n;
int w[N],dp[N][3];
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i ++)
scanf("%d",&w[i]);
memset(dp,-0x3f,sizeof(dp));
dp[0][0] = 0;
for(int i = 1;i <= n;i ++)
{
dp[i][0] = max(dp[i - 1][0],dp[i - 1][2]);
dp[i][1] = max(dp[i - 1][1],dp[i - 1][0] - w[i]);
dp[i][2] = dp[i - 1][1] + w[i];
}
printf("%d\n",max(dp[n][0],dp[n][2]));
return 0;
}
5:小国王
这道题显然是一道状压Dp,主要是为了练习状压中的状态在转移过程中的二进制变化
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 12,M = 1 << 10,K = 110;
typedef long long ll;
int n,m;
ll dp[N][K][M],cnt[M];
vector<int> state;
vector<int> heap[M];
bool check(int state)
{
for(int i = 0;i < n;i ++) //连续两个数不能同时放上国王
{
if((state >> i & 1) && (state >> i + 1 & 1)) return false;
}
return true;
}
int count(int state)
{
int res = 0;
for(int i = 0;i < n;i ++)
res += state >> i & 1;
return res;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 0;i < 1 << n;i ++) // 统计所有状态
{
if(check(i))
{
state.push_back(i);
cnt[i] = count(i);
}
}
// state.size() 每行合法数的总数
for(int i = 0;i < state.size();i ++)
{
for(int j = 0;j < state.size();j ++)
{
int a = state[i],b = state[j];
if((a & b) == 0 && check(a | b)) heap[i].push_back(j); // 上下不能相连
}
}
dp[0][0][0] = 1;
for(int i = 1;i <= n + 1;i ++)
{
for(int j = 0;j <= m;j ++)
{
for(int a = 0;a < state.size();a ++)
{
for(int b : heap[a])
{
int c = cnt[state[a]];
if(j >= c)
{
dp[i][j][a] += dp[i - 1][j - c][b];
}
}
}
}
}
printf("%lld\n",dp[n + 1][m][0]);
return 0;
}
6:凸多边形的划分
这道题是一道很不好想到的区间Dp主要是不要看范围很小但是最后还要加上高精度还要加上数学的推导才能解决
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 55;
int n;
int w[N];
vector<int> f[N][N];
bool cmp(vector<int> &a, vector<int> &b)
{
if (a.size() != b.size()) return a.size() < b.size();
for (int i = a.size() - 1; i >= 0; i -- )
if (a[i] != b[i])
return a[i] < b[i];
return true;
}
vector<int> add(vector<int> a, vector<int> b)
{
vector<int> c;
int t = 0;
for (int i = 0; i < a.size() || i < b.size(); i ++ )
{
if (i < a.size()) t += a[i];
if (i < b.size()) t += b[i];
c.push_back(t % 10);
t /= 10;
}
while (t) c.push_back(t % 10), t /= 10;
return c;
}
vector<int> mul(vector<int> a, LL b)
{
vector<int> c;
LL t = 0;
for (int i = 0; i < a.size(); i ++ )
{
t += b * a[i];
c.push_back(t % 10);
t /= 10;
}
while (t) c.push_back(t % 10), t /= 10;
return c;
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
for (int len = 3; len <= n; len ++ )
{
for (int l = 1, r; (r = l + len - 1) <= n; l ++ )
{
for (int k = l + 1; k < r; ++ k)
{
auto new_val = mul(mul({w[l]}, w[k]), w[r]);
new_val = add(add(new_val, f[l][k]), f[k][r]);
if (f[l][r].empty() || cmp(new_val, f[l][r])) f[l][r] = new_val;
}
}
}
auto res = f[1][n];
for (int i = res.size() - 1; i >= 0; i -- ) printf("%d", res[i]);
puts("");
return 0;
}
7:关闭路灯
这道题是一道区间Dp的题目,这道题的唯一难点是如何将区间Dp和贪心结合起来,可以先将Dp数组预处理出来即可
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 55;
int n,c;
int loc[N],p[N],dp[N][N][2];
int cal(int i,int j,int l,int r)
{
return (loc[j] - loc[i]) * (p[l] + p[n] - p[r - 1]);
}
int main()
{
scanf("%d%d",&n,&c);
memset(p,0,sizeof(p));
memset(dp,0x3f,sizeof(dp));
for(int i = 1;i <= n;i ++)
{
int a,b;
scanf("%d%d",&a,&b);
loc[i] = a;
p[i] = p[i - 1] + b;
}
dp[c][c][1] = dp[c][c][0] = 0;
for(int j = c;j <= n;j ++)
{
for(int i = j - 1;i > 0;i --)
{
dp[i][j][0] = min(dp[i + 1][j][0] + cal(i,i + 1,i,j + 1),dp[i + 1][j][1] + cal(i,j,i,j + 1));
dp[i][j][1] = min(dp[i][j - 1][0] + cal(i,j,i - 1,j),dp[i][j - 1][1] + cal(j - 1,j,i - 1,j));
}
}
printf("%d\n",min(dp[1][n][0],dp[1][n][1]));
return 0;
}