古典的なナップザック問題
- 0-1 バックパック
- フルバックパック
- 複数のバックパック
- グループバックパック
0-1 バックパック
アイテムが N 個あり、容量 M のナップザックがあり、各アイテムが 1 回選択され、占有スペースが v で、値が w です。ナップザックを最大限に満たした場合の最大値はいくらですか?
#include<bits/stdc++.h>
using namespace std;
const int N = 1005;
int f[N][N],w[N],v[N];
int n,m;
int temp;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)// i 是遍历的物品
for(int j=0;j<=m;j++)// j 是当前容积
{
f[i][j] = f[i-1][j];
// 本质上是当j>=v[i]时,f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i])
if(j>=v[i])
{
f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);
}
temp = max(temp,f[i][j]);
}
cout<<temp<<endl;
return 0;
}
///
//一维解法
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N = 1005;
int f[N],v[N],w[N],temp;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)//枚举物品
for(int j=m;j>=v[i];j--)//枚举物品的容积 使用j做下标 因为容积有限 要倒着...?
{
// j=v[i]~m ->f[i][j-v[i]]是不对的!
// j=m~v[i] ->f[i-1][j-v[i]]
f[j] = max(f[j],f[j-v[i]]+w[i]);
temp = max(temp, f[j]);
}
cout<<temp<<endl;
return 0;
}
フルバックパック
各アイテムを無制限に選択可能
#include<bits/stdc++.h>
using namespace std;
const int N = 1005;
int f[N][N],w[N],v[N];
int n,m;
int temp;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
for(int k=0;k*v[i]<=j;k++)//选几次
f[i][j] = max(f[i][j],f[i-1][j-k*v[i]]+w[i]);
cout<<f[n][m]<<endl;
return 0;
}
///
//优化两个循环//通过递推得到公式
// f[i][j] = max( f[i-1][j],f[i-1][j-v[i]]+w[i],f[i-1][j-v[i]*2]+2*w[i],...,)
// f[i][j-v[i]] = max( f[i-1][j-v[i]] ,f[i-1][j-v[i]*2]+w[i],...,)
//=> f[i][j] = max( f[i-1][j], f[i][j-v[i]]+w[i] )
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
{
f[i][j] = f[i-1][j];
if(j>=v[i])
f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);
}
cout<<f[n][m]<<endl;
return 0;
}
///
//优化成为一维
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N = 1005;
int f[N],v[N],w[N],temp;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)//枚举物品
for(int j=v[i];j<=m;j++)//枚举物品的容积
{
f[j] = max(f[j],f[j-v[i]]+w[i]);
temp = max(temp, f[j]);
}
cout<<temp<<endl;
return 0;
}
複数のバックパック
各アイテムには固定数のオプションがあります
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N = 105;
int f[N][N],v[N],w[N],s[N];
int temp;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i]>>s[i];
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
for(int k=0;k*v[i]<=j && k<=s[i];k++)
{
// 注意这里的出现的一个问题
// 不选的时候怎么表示???
// 进入k循环之后k=0代表不选 所以k=0~s[i]它把选和不选都包含了...
// 所以不用特地写出来了
if(j>=v[i]*k)
{
f[i][j] = max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k);
}
}
cout<<f[n][m]<<endl;
return 0;
}
///
/// 优化
// f[i][j] = max( f[i-1][j],f[i-1][j-v[i]]+w[i],f[i-1][j-v[i]*2]+2*w[i],...,f[i-1][j-v[i]*s[i]]+s[i]*w[i])
// f[i][j-v[i]] = max( f[i-1][j-v[i]] ,f[i-1][j-v[i]*2]+w[i],...,f[i-1][j-v[i]*s[i]]+(s[i]-1)*w[i],f[i-1][j-v[i]*(s[i]+1)]+s[i]*w[i]) )
//=> f[i][j] = max( f[i-1][j], f[i][j-v[i]]+w[i] )
多出来一项 不能被削掉 需要用二进制 1 2 4 8 ,..,512 用二进制数表示原来的数
/// 把多个物品进行分组打包... 每组只能选一个...
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N = 20005;
int f[N],v[N],w[N];
int temp;
// 1 2 4 8 ..., 2^k , C
// C < 2^(k+1)
// 可以表示: 0~S
// 分好组之后对这些组做一次0-1背包
int main()
{
cin>>n>>m;
int cnt = 0;
for(int i=1;i<=n;i++)
{
int a,b,c;
cin>>a>>b>>c;
//从1开始 1,2,4,8,...
int k = 1;
while(k<=c)
{
cnt++;
v[cnt] = a*k;
w[cnt] = b*k;
// 个数-K
c = c - k;
k = k*2;
}
//剩下就是C
if(c>0)
{
cnt++;
v[cnt] = a*c;
w[cnt] = b*c;
}
}
// cnt 分成几组?
n = cnt;
for(int i=1;i<=n;i++)
for(int j=m;j>=v[i];j--)
f[j] = max(f[j],f[j-v[i]]+w[i]);
cout<<f[m]<<endl;
return 0;
}
グループバックパック
グループ内で選択できる項目は 1 つだけです
// 枚举第i组物品选几个?->完全背包
// 枚举第i组物品选哪个?->分组背包 第k个 几重循环???
// f[i,j] = max(f[i,j],f[i,j-v[i,k]]+w[i,k])
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N =105;
int f[N],s[N],w[N][N],v[N][N];
int main()
{
cin>>n>>m;
// 一维的先...
for(int i=1;i<=n;i++)
{
cin>>s[i];//组里面的物品数
for(int j=0;j<s[i];j++)
cin>>v[i][j]>>w[i][j];
}
for(int i=1;i<=n;i++)
for(int j=m;j>=0;j--)
for(int k = 0;k<s[i];k++)
if(j>=v[i][k])
f[j] = max(f[j],f[j-v[i][k]]+w[i][k]);
cout<<f[m]<<endl;
return 0;
}
初心者向けの DP に関する質問
階段を登る問題は実際にはフィボナッチ数列の変形なのでしょうか?
746. 最小限のコストで階段を登る
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
vector<int> dp(cost.size());
dp[0] = cost[0];
dp[1] = cost[1];
for(int i=2;i<cost.size();i++)
{
dp[i] = min(dp[i-1],dp[i-2])+cost[i];
}
// 为什么不直接输出dp[n-1]? 因为是前两步的最小值 这样直接输出会漏掉一种情况
return min(dp[dp.size()-1],dp[dp.size()-2]);
// return dp[dp.size()-1];
}
};
62. 異なる道
ヤン・フイ・トライアングルはまた君だ!
実際、それは組み合わせた数字の順列三角形です。
class Solution {
public:
int uniquePaths(int m, int n) {
// dp[i][j] => (0,0)到(i,j)的路径种数
// 转移状态: dp[i][j-1] + dp[i-1][j] => dp[i][j] // 不同路径
// end = (m-1,n-1)
// (0,0)->(i,0)路径种数 all 1 (0,0)->(0,i) all 1
// (0,0)->(1,0) = 1
// (0,0)->(2,0) = 1
// 初始化dp vector<vector<T>> a(N,v) 类似于reshape v是一个vector<T>数组
// vector<T> a(n,0) 初始化为全0的大小为n的T类型数组
vector<vector<int>> dp(m, vector<int>(n,0));
for(int i=0;i<m;i++) dp[i][0] = 1;
for(int i=0;i<n;i++) dp[0][i] = 1;
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
dp[i][j] = dp[i-1][j] + dp[i][j-1];
return dp[m-1][n-1];
}
};
一维数组 每次走到一个格子都是1种
///保存每次走到某列的结果 dp[i] 走到第i列时的总数
class Solution {
public:
int uniquePaths(int m, int n) {
vector<int> dp(n,1);
for(int j=1;j<m;j++)
for(int i=1;i<n;i++)
dp[i] += dp[i-1];
return dp[n-1];
}
};
63. 異なる道 II
変形したバージョンのパスには障害物があるため、計算を実行する前に障害物を取り除く必要があります。
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
//获取行列
int row = obstacleGrid.size();
int column = obstacleGrid[0].size();
vector<vector<int>> dp(row, vector<int> (column, 0));
// 标记第一列和第一行的初始值
for(int i=0;i<row && obstacleGrid[i][0] == 0;i++) dp[i][0] = 1;
for(int i=0;i<column && obstacleGrid[0][i] == 0;i++) dp[0][i] = 1;
for(int i=1;i<row;i++)
for(int j=1;j<column;j++)
//这里要限制当前的格子不是障碍物才行走... 漏掉这个会错
if(obstacleGrid[i][j]==0)
dp[i][j] = dp[i-1][j] + dp[i][j-1];
return dp[row-1][column-1];
}
};
別のパス 3 もありますが、DFS が使用されるため、ここではこの質問には触れません。
X 個の模擬質問
レンタカーを借りてグリーン島に乗りましょう
1台に2人まで乗車可能です 最大積載量はM、人数はNです N人の体重情報を入力してください
最小の車は何ですか? 0<=N<=1e6
/
//第一种 用for来做...
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int main()
{
int m, n;
cin >> m >> n;
vector<int> w(n);
for(int i=0; i<n; i++)
{
cin >> w[i];
}
sort(w.begin(), w.end());
int count = 0;
int i,j;
for(i=0,j=n-1; i<j; )
{
int cur = w[i] + w[j];
if(cur > m)
{
j--;
count++;
}
else
{
i++;
j--;
count++;
}
}
if(i == j)//只剩下一个人
{
count++;
}
cout << count << endl;
return 0;
}
///
//第二种 用while
#include<iostream>
#include<algorithm>
#include<vector>
//平时可以用万能头,考试的时候应该不能用
using namespace std;
int m,n;
// 逻辑模拟...0-0真的菜啊
//一车最多坐两人 最大载重M 人数是N
//问最小几辆车
int main()
{
vector<int> w;
cin>>m>>n;
for(int i=0;i<n;i++)
{
int x;
cin>>x;
w.push_back(x);
}
// 对权重排序
// 其实已经想到这里了
sort(w.begin(),w.end());
//设置两个遍历对象
int l = 0, r = w.size()-1;
int res = 0;
int cur = w[l]+w[r];
// 测试的时候没有弹出 已经使用的权重...
while(l<r)
{
if(cur>m)
{
//当前重量>m 移动右边的,因为左边的越小越可能满足条件
r-=1;
res+=1;//说明这个w[r]自己坐一辆车
cur = w[l]+w[r];//更新当前的重量
}
else
{
//当前重量<=m 移动左边和右边
r-=1;
l+=1;
res+=1;//两个人坐一辆车
cur = w[l]+w[r];
}
}
if(l==r) res+=1;//?为啥...两者相遇=>总会剩下一个人
cout<<res<<endl;
return 0;
}
各庭園には最大 3 つの出入り経路があり、外次数と内次数が 3 であることを示しています。
4色しか染められないので、つながった庭園ごとに色が異なります
実現可能な解を出力(すべての実現可能な解の合計数を出力)
// 这里直接给出邻接的坐标... 不是传入g[i][j] = 1 表示双向有边
// path:[[1,2] [2,3] [3,1]]====> 这里需要 x->y y->x ===>
// 实际上是这么存储的 (1->2,2->1) (2->3,3->2) (1->3,3->1)
// g[0] = {2}//代表第一个花园的邻接花园集合
// g[1] = {3}
// g[2] = {1}
// 所以x的下标需要映射y的坐标 所以y的下标需要映射x的坐标 它们都是从下标0开始所以第一下标要-1
class Solution {
public:
vector<int> gardenNoAdj(int N, vector<vector<int>>& paths) {
vector<vector<int>> g(N);
vector<int> ans(N,0);
for(int i=0;i<paths.size();i++)
{
// 这里取的是下标起始是0
g[paths[i][0]-1].push_back(paths[i][1]);
g[paths[i][1]-1].push_back(paths[i][0]);
}
for(int i=0;i<N;i++)
{
//遍历x->y 表
int c[4] = {
0};//染色数组
for(int j=0;j<g[i].size();j++)//遍历当前的花园的邻接花园
if(ans[g[i][j]-1]!=0)// ans[g[i][j]-1]的值: 第i个花园的邻接花园j的颜色编号
c[ ans[g[i][j]-1]-1 ] += 1;// c是被选择的颜色的次数,c[0] = N 第一种颜色被选N次
for(int k = 0;k<4;k++)
{
// 这里k从0开始 颜色编号从1开始 所以颜色编号要+1
if(c[k] == 0)//当颜色没被使用的话...
{
ans[i] = k+1;
break;
}
}
}
return ans;
}
};
全シナリオの合計を出力する場合:
class Solution {
public:
vector<vector<int>> gardenNoAdj(int N, vector<vector<int>>& paths) {
vector<vector<int>> g(N);
vector<int> ans(N,0);
vector<vector<int>> res;
for(int i=0;i<paths.size();i++)
{
g[paths[i][0]-1].push_back(paths[i][1]);
g[paths[i][1]-1].push_back(paths[i][0]);
}
dfs(g, ans, res, 0);
return res;
}
private:
void dfs(vector<vector<int>>& g, vector<int>& ans, vector<vector<int>>& res, int index) {
if (index == ans.size()) {
res.push_back(ans);
return;
}
vector<bool> used(5, false);
for (int j = 0; j < g[index].size(); j++) {
if (ans[g[index][j] - 1] != 0) {
used[ans[g[index][j] - 1]] = true;
}
}
for (int k = 1; k <= 4; k++) {
if (!used[k]) {
ans[index] = k;
dfs(g, ans, res, index + 1);
ans[index] = 0;
}
}
}
};
完璧な位置----> LeetCode 変形
最初は文字列が決まった順番で並んでいると思ってとても簡単だと思っていたのですが、後でそれが違うことに気づき、LeetCodeの解決策を読んでやっと書き方が分かりました!
#include<iostream>
#include<string>
#include<map>
using namespace std;
map <char,int> mp;
int main()
{
string s;
cin>>s;
int left = 0, right = 0;
for(int i=0;i<s.size();i++)
mp[s[i]]+=1;
int ans = 0x3f3f3f3f;
int len = int(s.size()/4);
//这里要特判一下 不然输出的就是很大的ans
if(mp['W']==len && mp['A']==len && mp['D']==len && mp['S']==len)
{
cout<<0<<endl;
return 0;
}
for(int right=0;right<s.size();right++)
{
mp[s[right]] -=1;// 有元素进入窗口 然后在窗外面的要减一
while(mp['W']<= len && mp['S']<=len && mp['A']<=len && mp['D']<=len)
{
ans = min(ans,right-left+1);
mp[s[left++]]+=1;
//求最小子串长 所以符合条件之后移动左端点 元素减少 窗外面的增加
}
}
cout<<ans<<endl;
return 0;
}
まずはこれを見てみると、かなり理解できると思います!神様からの贈り物に感謝します!
1234. 部分文字列を置換してバランスのとれた文字列を取得する
'Q'、'W'、'E'、'R' の 4 つの文字だけを含む長さ n の文字列があります。
4 つの文字すべてが文字列内に正確に n/4 回出現する場合、それは「バランスのとれた文字列」です。
このような文字列が与えられた場合、元の文字列をs
「部分文字列の置換」によって「s
バランスの取れた文字列」にしてください。
「置換される部分文字列」と同じ長さの他の文字列を使用して、置換を完了できます。
置換される部分文字列の最小可能な長さを返してください。
元の文字列自体がバランスの取れた文字列である場合は 0 を返します。
ここで、入力文字列の長さは 4 の倍数であることに注意してください。
したがって、最初に長さを判断する必要はありませんが、ここでは文字列内のすべての文字の出現数 == n/4 を示します。順序は必要ありません。
したがって、決まった順序で判断することはできません。QWERもリーズナブル、REWQ、EWQRもリーズナブル…。
なんとずるい!
担当者は、「置換する領域として小さなスライド窓を指定し、窓の外側の各文字の番号を判断します」と述べました。
現在の文字数が n/4 以下の場合、バランスの取れた文字列になるための条件が満たされますが、それ以外の場合は条件が満たされません。
私の頭は疑問符でいっぱいですか?なぜ?!
ああ、よく見てみると満足回数はn/4なので、それ以上になってからはn/4になってはいけない、外側の文字は修正できないので…、これだけです。ウィンドウ内の文字は変更でき、このときウィンドウのサイズも変更されます。トラバース数が増加すると変化します。
ウィンドウを維持するには、左の端点を left として、右の端点を right として使用します。right++ の場合、ウィンドウは 1 つ多くの要素に入り、left++ の場合、ウィンドウは 1 要素減少しますが、ウィンドウの外側の要素は計算する必要があるため、配列統計 方向転換する時が来ました...
right++ の場合、ウィンドウの外側の現在の要素数–、left++ の場合、ウィンドウ ++ の外側の現在の要素数
同方向ダブルポインタ
class Solution {
public:
int balancedString(string s) {
map <char,int> mp;
int len = int(s.size()/4);
for(int i=0;i<s.size();i++)
mp[s[i]]+=1;
if(mp['Q']==len && mp['E'] == len && mp['R'] == len && mp['W'] == len)
return 0;
int ans = 0x3f3f3f3f, left = 0;
for(int right = 0;right<s.size();right++)
{
mp[s[right]]-=1;
//只要满足条件就持续缩小窗口长度 即左指针右移动
while(mp['Q']<=len && mp['E'] <= len && mp['R'] <= len && mp['W'] <= len)
{
ans = min(ans,right-left+1);
mp[s[left++]]+=1;//窗口外的元素增加 所以次数也增加
}
}
return ans;
}
};
同様の問題...スライディングウィンドウを使用することもできます
567. 文字列の順列
ハッシュテーブル初級
- まず、部分文字列 s1 の順列が別の文字列 s2 に含まれている場合、一致する部分文字列の長さは等しくなります。
- 列挙型の順列? いいえ、現在の部分文字列の文字数が一致する部分文字列の文字数と同じである必要があるだけです。これは、列挙には時間がかかり、組み合わせることができる順列の数だけを知っていればよいためです。
この2つのコツを掴んで、上記の方法を使って解決してみましょう。
違いは、このウィンドウのサイズは変更されず、前のウィンドウのサイズが変更されることです。ここでは、ウィンドウ サイズの要素数の判断に焦点を当てます。
既知であると仮定します: 初期文字列s1
:abcdabc
と一致する文字列s2
:da
スライディング ウィンドウのサイズを直接指定することは、s2.size()
2 つのハッシュ テーブルを初期化しh
、その中のすべての要素の出現数hash
のみをカウントし、スライドするときに... 要素の数をカウントします。ウィンドウを右に移動すると、ウィンドウが数値 ++ の要素の数、削除された要素の数 –以前に実行されているため、この操作は実行されないことに注意してください...一致出力または 出力、しかしこの時点で、最後のウィンドウが一致していないことがわかりました...したがって、最終的にはまだ判断する必要があります。両方が同じであれば、一致することを意味します...マッピング関係は現在のものです[0-s2.size())
[d,a] -> len = 2
hash={'a':1,'b':0,...,'d':1,...,'z':0}
[a,b,c,d,a,b,c] -> h = {'a':1,'b':1,'c':0,'d':0,...}
s1上
[[a,b],c,d,a,b,c]->
[a,[b,c],d,a,b,c] -> h['a']--,h['c']++
[a,b,[c,d],a,b,c] -> h['b']--,h['d']++
[a,b,c,[d,a],b,c] -> h['c']--,h['a']++
h={'a':1,'b':0,'c':0,'d':1} ->hash
true
false
h==hash
h[s[i-len]]--,h[s[i]]++
class Solution {
public:
bool checkInclusion(string s1, string s2) {
//特判s1>s2的情况 之前没有特判...
if(s1.size()>s2.size()) return false;
int win_size = s1.size();
// 初始化hash1,hash2两个哈希表 并保存所有字母的映射关系
vector<int> hash1(26,0);
vector<int> hash2(26,0);
for(int i=0;i<win_size;i++)
{
hash1[s1[i]-'a']+=1;
hash2[s2[i]-'a']+=1;
}
for(int i=win_size;i<s2.size();i++)
{
//主要操作的是s2的匹配情况
if(hash2 == hash1) return true;
hash2[s2[i-win_size]-'a']-=1;
hash2[s2[i]-'a']+=1;
}
// vector可以直接比较两个数组的大小情况
return hash1==hash2;
}
};
テキストメッセージの数
テキスト メッセージの数を購入し、予算 M とサイズ N の配列を指定します。添え字は 1 から始まり、価格 i で送信できるテキスト メッセージの数を表し、送信できるテキスト メッセージの最大数を求めます。予算内でしょうか?
入力:
6 5
10 20 30 40 60
出力:
70
分析: 予算は 6 元で、1 元で 10 通、2 元で 20 通、3 元で 30 通、4 元で 40 通、5 元で 60 通のテキスト メッセージを送信できます。(60+10=70) 個のテキスト メッセージを送信するには、合計 (5+1 元) が費やされます。
えっと、バックパックいっぱいですか?
// 物品可以选无限次,但是总重量必须在容器范围内,求最大价值?
// 物品就是短信,价值是几条短信,容器大小是预算数目
// 速通!
#include<iostream>
using namespace std;
const int N =105;
int n,m;
int v[N],w[N],f[N];
int main()
{
cin>>m>>n;
for(int i=1;i<=n;i++)
{
cin>>w[i];
v[i]=i;
}
for(int i=1;i<=n;i++)
for(int j=v[i];j<=m;j++)
f[j] = max(f[j],f[j-v[i]]+w[i]);
cout<<f[m]<<endl;
return 0;
}