第三周训练题单
数字三角形
#include <bits/stdc++.h>
using namespace std;
#define int long long
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n;
while( cin >> n){
for( int i = 1 ; i <= n ; i ++ ){
for( int j = 1 ; j <= i ; j ++ )
cout << j << " ";
cout <<"\n";
}
}
return 0;
}
走楼梯
#include <bits/stdc++.h>
using namespace std;
#define int long long
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n;
cin >> n;
vector<int> f(n+1);
f[0] = f[1] = 1;
for( int i = 2 ; i <= n ; i ++ )
f[i] = f[i-1] + f[i-2];
cout << f[n] << "\n";
return 0;
}
最大子串和
#include <bits/stdc++.h>
using namespace std;
#define int long long
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n , res = 0;
cin >> n;
for( int i = 1 , x , cnt = 0; i <= n ; i ++ ){
cin >> x;
cnt = max( 0ll , cnt + x );
res = max( res , cnt );
}
cout << res << "\n";
return 0;
}
失衡天平
\(f[i][j]\)从前 i 个物品中选择,且重量差为\(j\)时可以选择到的最大重量。转移的时候其实考虑三种情况
- \(a_i\)不要
- \(a_i\)要,且放在之前更重的一侧,此时\(j\)会变大。
- \(a_i\)要,且放在之前更轻的一侧,此时\(j\)会变小,但是\(j\)可能会变成负数,所以要取绝对值
#include <bits/stdc++.h>
using namespace std;
#define int long long
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n, k, m = 0;
cin >> n >> k;
vector<int> a(n + 1);
for (int i = 1; i <= n; i++)
cin >> a[i], m += a[i];
vector f(n + 1, vector<int>(m + 1, INT_MIN));
f[0][0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++){
f[i][j] = f[i-1][j];
f[i][j] = max( f[i][j] , f[i-1][ abs( j - a[i] ) ] + a[i] );
if( j + a[i] <= m ) f[i][j] = max( f[i][j] , f[i-1][j+a[i]] + a[i] );
}
}
int res = 0;
for( int i = 0 ; i <= k ; i ++ )
res = max( res , f[n][i] );
cout << res << "\n";
return 0;
}
多重背包
#include <bits/stdc++.h>
using namespace std;
#define int long long
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n , m;
cin >> n >> m;
vector<int> t(n+1) , w(n+1) , v(n+1);
for( int i = 1 ; i <= n ; i ++ )
cin >> t[i] >> w[i] >> v[i];
vector f( n+1 , vector<int>(m) );
int res = 0;
for( int i = 1 ; i <= n ; i ++ )
for( int j = 0 ; j <= m ; j ++ ){
f[i][j] = f[i-1][j];
for( int k = 1 ; k <= t[i] && k * w[i] <= j ; k ++ )
f[i][j] = max( f[i][j] , f[i-1][ j - k * w[i] ] + k * v[i] );
res = max( res , f[i][j] );
}
cout << res << "\n";
return 0;
}
货币系统
从小到大逐个检查当前货币能否被替代。
#include <bits/stdc++.h>
using namespace std;
#define int long long
void solve() {
int n;
cin >> n;
vector<int> a(n);
for (auto &i: a)
cin >> i;
sort(a.begin(), a.end());
vector<int> v(a.back() + 1);
int res = 0;
for (auto i: a) {
if (v[i]) continue;
v[i] = 1, res++;
for (int j = 0; i + j < v.size(); j++)
if (v[j] ) v[i + j] = 1;
}
cout << res << "\n";
}
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int t;
cin >> t;
for (; t; t--)
solve();
return 0;
}
合并回文子串
\(f[lA][rA][lB][rB]\)表示字符串\(A[lA,rA]\)和\(B[lB,rB]\)能否拼成回文串,然后考虑区间 dp 去扩展。因为要求了串内是顺序不能改变,所以新扩展的只能是\((A[lA],A[rA]),(B[lB],B[rB]),(A[lA],B[rB]),(B[lB],A[rA])\)四种情况。逐个去判断能够扩展即可。注意所有长度小于等于 1 的串都可以认为是回文串。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 55;
int f[N][N][N][N];
void solve() {
string a, b;
cin >> a >> b;
int n = a.size(), m = b.size();
memset( f , 0 , sizeof(f) );
int res = 0;
for (int lenA = 0; lenA <= n; lenA++)
for (int lenB = 0; lenB <= m; lenB++)
for (int lA = 1, rA = lenA; rA <= n; lA++, rA++)
for (int lB = 1, rB = lenB; rB <= m; lB++, rB++) {
if (lenA + lenB <= 1) f[lA][rA][lB][rB] = 1;
else {
if (rA > 0 && a[lA - 1] == a[rA - 1])
f[lA][rA][lB][rB] |= f[lA + 1][rA - 1][lB][rB];
if (rB > 0 && b[lB - 1] == b[rB - 1])
f[lA][rA][lB][rB] |= f[lA][rA][lB + 1][rB - 1];
if (rB > 0 && a[lA - 1] == b[rB - 1])
f[lA][rA][lB][rB] |= f[lA + 1][rA][lB][rB - 1];
if (rA > 0 && b[lB - 1] == a[rA - 1])
f[lA][rA][lB][rB] |= f[lA][rA - 1][lB + 1][rB];
}
if (f[lA][rA][lB][rB]) res = max(res, lenA + lenB);
}
cout << res << "\n";
}
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int t;
cin >> t;
for (; t; t--)
solve();
return 0;
}
没有上司的舞会
\(f[i][0/1]\)表示\(i\)的子树中\(i\)不选或选的最大收益
#include <bits/stdc++.h>
using namespace std;
#define int long long
vector<int> a;
vector<array<int,2>> f;
vector<vector<int>> e;
void dfs( int i){
f[i][1] = a[i];
for( int j : e[i] ){
dfs(j);
f[i][1] += f[j][0];
f[i][0] += max(f[j][0] , f[j][1]);
}
return ;
}
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n;
cin >> n;
a=vector<int>(n+1);
f = vector<array<int,2>>(n+1);
e = vector<vector<int>>(n+1);
for( int i = 1 ; i <= n ; i ++ )
cin >> a[i];
int root = n * (n+1) / 2;
for( int i = 1 , x , y ; i < n ; i ++ )
cin >> x >> y , e[y].push_back(x) , root -= x;
dfs(root);
cout << max( f[root][0] , f[root][1] );
return 0;
}
小G有一个大树
统计子树大小\(cnt_i\),这\(f_i=\max( n - cnt_i,cnt_j)\),其中\(j\)是\(i\)的子节点
#include <bits/stdc++.h>
using namespace std;
#define int long long
int n;
vector<vector<int>> e;
vector<int> cnt, fa;
void dfs(int x) {
cnt[x] = 1;
for (auto y: e[x]) {
if (y == fa[x]) continue;
fa[y] = x, dfs(y);
cnt[x] += cnt[y];
}
return;
}
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
while (cin >> n) {
e = vector<vector<int>>(n + 1), cnt = vector<int>(n + 1, -1), fa = vector<int>(n + 1, -1);
for (int x, y, i = 1; i < n; i++)
cin >> x >> y, e[x].push_back(y), e[y].push_back(x);
dfs(1);
int res = -1, val = INT_MAX;
for (int i = 1, f; i <= n; i++) {
f = n - cnt[i];
for (auto y: e[i])
if( y != fa[i] ) f = max(f, cnt[y]);
if (val > f) val = f, res = i;
}
cout << res << " " << val << "\n";
}
return 0;
}
石子合并
首先破环成链,然后然后这道题就可以转换成区间 dp。
先考虑最大得分
设\(f[l][r]\)表示把\([l,r]\)合并的最大得分,枚举合并的中间点\(k\),则\(f[l][r] = \max( f[l][k] + f[k+1][r] +\sum a[i])\)
这里注意,当前区间一定是从更短的区间转移过来,所以枚举区间的时候,先枚举区间长度,然后根据左端点算出右端点,同样是\(O(N^2)\)的枚举,这样可以保证更短的区间一定会更早的被枚举。最小得分操作基本相同。
#include <bits/stdc++.h>
using namespace std;
const int N = 405;
int a[N], f[N][N], g[N][N];
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n;
cin >> n;
memset(g, 0x3f, sizeof g);
for (int i = 1; i <= n; i++) cin >> a[i], a[i + n] = a[i];
for (int i = 1; i <= n * 2; i++) a[i] += a[i - 1], g[i][i] = 0;
for (int len = 2; len <= n; len++) {
for (int l = 1, r = len; r <= 2*n; l++, r++) {
for (int k = l, W = a[r] - a[l - 1]; k < r; k++) {
f[l][r] = max(f[l][r], f[l][k] + f[k + 1][r] + W);
g[l][r] = min(g[l][r], g[l][k] + g[k + 1][r] + W);
}
}
}
int res = INT_MIN, ans = INT_MAX;
for (int l = 1, r = n; l <= n; l++, r++)
res = max(res, f[l][r]), ans = min(ans, g[l][r]);
cout << ans << "\n" << res;
return 0;
}
炮兵阵地
首先统计出所有可能的合法放置方法。
设状态\(f[i][j][k]\)表示前\(i\)行且第\(i,i-1\)行状态是\(j,k\)情况下最多可以放的炮兵数量。
对于第\(i\)我们可以暴力的枚举出第\(i,i-1,i-2\)行的状态\(j,k,l\),判定合法后就可以转移\(f[i][j][k]=\sum f[i-1][k][l] + val[j]\)
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define lowbit(x) ( x & -x )
typedef bitset<4> T;
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n, m;
cin >> n >> m;
int M = (1 << m) - 1;
vector<int> a(n);
for (int i = 0; i < n; i++) {
string s;
cin >> s;
for (auto j: s)
a[i] = (a[i] << 1) | (j == 'H');
}
vector<array<int, 2>> P;
for (int i = 0, j, t; i <= M; i++) {
if (i & ((i << 1) | (i << 2) | (i >> 1) | (i >> 2))) continue;
j = i, t = 0;
while (j) t++, j -= lowbit(j);
P.push_back({i, t});
}
int N = P.size();
vector f(N, vector(N, 0ll)), g(N, vector(N, 0ll));
for (int i = 0; i < N; i++) {
if (a[1] & P[i][0]) continue;
for (int j = 0; j < N; j++) {
if (P[j][0] & (P[i][0] | a[0])) continue;
g[i][j] = P[j][1] + P[i][1];
}
}
for (int i = 2; i < n; i++) {
for (int j = 0; j < N; j++) {
if (P[j][0] & a[i]) continue;
for (int k = 0; k < N; k++) {
if (P[k][0] & (a[i - 1] | P[j][0])) continue;
for (int l = 0; l < N; l++) {
if (P[l][0] & (a[i - 2] | P[k][0] | P[j][0])) continue;
f[j][k] = max(f[j][k], g[k][l] + P[j][1]);
}
}
}
g.swap(f), f = vector(N, vector(N, 0ll));
}
int res = 0;
for (const auto &i: g)
res = max(res, *max_element(i.begin(), i.end()));
cout << res << "\n";
return 0;
}
传纸条
一正一反找两条路径,等价于正着直接找两条不相同的路径。因为\((n,m)\)的权值为零,所以我们可以直接找两条路分别到达\((n,m-1),(n-1,m)\)。如何实现过程中不想交?保证在每一行第一条路径到达\((i,j)\)后,第二条路径只能到达\((i,j+1)\)及其右侧的点即可。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 55;
int g[N][N] , f[N][N][N][N];
int32_t main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n, m;
cin >> n >> m;
for( int i = 1 ; i <= n ; i ++ )
for( int j = 1 ; j <= m ; j ++)
cin >> g[i][j];
for( int i = 1 ; i <= n ; i ++ )
for( int j = 1 ; j <= m ; j ++ )
for( int x = 1 ; x <= n ; x ++ )
for( int y = j+1 ;y <= m ; y ++ ){
f[i][j][x][y] = max( {f[i][j-1][x][y-1] , f[i][j-1][x-1][y] , f[i-1][j][x][y-1],f[i-1][j][x-1][y]}) + g[i][j] + g[x][y];
}
cout << f[n][m-1][n-1][m];
return 0;
}