CF#892 div2
过了abc,卡在了d
A United We Stand
将数组a拆分乘数组b和c,使得满足任意c[i]不是b[j]的因子,b和c中至少存在一个数。
如果不能输出-1
法一:
巧妙构造:
因为一个大的数不可能是一个小的数的因子,所以我把最大的数(最大的数数量可能有很多个,需要全都放在c里面,因为两个相等的数之间也互为因子)放在c后,其他剩下的数放在b,即构造成功。
只有当所有的数都是最大的数(也就是a中所有数都相等),没有数放到b,输出-1
// 赛时代码
#include <bits/stdc++.h>
#define LL long long
#define ULL unsigned long long
#define x first
#define y second
#define endl "\n"
using namespace std;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int INF = 0x3f3f3f3f;
void solve()
{
int n;
cin >> n;
vector<int> a(n);
int maxv = 0;
for (int i = 0; i < n; i++)
{
cin >> a[i];
maxv = max(maxv, a[i]);
}
sort(a.begin(), a.end());
if (maxv == a[0])
{
cout << "-1" << endl;
return;
}
int pos = 0;
for (int i = 0; i < a.size(); i++)
if (a[i] == maxv)
{
pos = i;
break;
}
cout << pos << ' ' << n - pos << endl;
for (int i = 0; i < pos; i++)
cout << a[i] << ' ';
cout << endl;
for (int i = pos; i < a.size(); i++)
cout << a[i] << ' ';
cout << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--)
{
solve();
}
return 0;
}
法二: from jiangly
SCC
如果 x % y == 0,连接一条 x -> y 的有向边。
一个强连通分量里面的点互相之间都满足 x % y == 0,所以一个强连通分量里面的点需要全放在b或者c里面。
如果整个图只有一个强连通分量的话,故输出-1
如果大于一个的话,因为SCC后按照下标递减的顺序就是拓扑排序
所以 bel[i] == 0 => 表示的是只有入度没有出度的那个点
除了这个强连通分量里的点都不是这些点的因子,其他点都只可能是这些点的倍数
#include <bits/stdc++.h>
#define LL long long
#define ULL unsigned long long
#define x first
#define y second
#define endl "\n"
using namespace std;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int INF = 0x3f3f3f3f;
struct SCC
{
int n;
vector<vector<int>> adj;
vector<int> stk;
vector<int> dfn, low, bel;
int cur, cnt;
SCC() {}
SCC(int n)
{
init(n);
}
void init(int n)
{
this->n = n;
adj.assign(n, {});
dfn.assign(n, -1);
low.resize(n);
bel.assign(n, -1);
stk.clear();
cur = cnt = 0;
}
void addEdge(int u, int v)
{
adj[u].push_back(v);
}
void dfs(int x)
{
dfn[x] = low[x] = cur++;
stk.push_back(x);
for (auto y : adj[x])
{
if (dfn[y] == -1)
{
dfs(y);
low[x] = min(low[x], low[y]);
}
else if (bel[y] == -1)
{
low[x] = min(low[x], dfn[y]);
}
}
if (dfn[x] == low[x])
{
int y;
do
{
y = stk.back();
bel[y] = cnt;
stk.pop_back();
} while (y != x);
cnt++;
}
}
vector<int> work()
{
for (int i = 0; i < n; i++)
{
if (dfn[i] == -1)
{
dfs(i);
}
}
return bel;
}
};
void solve()
{
int n;
cin >> n;
SCC g(n);
vector<int> a(n);
for (int i = 0; i < n; i++)
cin >> a[i];
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
if (a[i] % a[j] == 0)
g.addEdge(i, j);
auto bel = g.work();
if (bel == vector(n, 0))
{
cout << -1 << endl;
return;
}
vector<int> b, c;
for (int i = 0; i < n; i++)
{
if (bel[i] == 0)
b.push_back(i);
else
c.push_back(i);
}
cout << b.size() << ' ' << c.size() << endl;
for(auto i : b)
cout << a[i] << " \n"[i == b.back()];
for(auto i : c)
cout << a[i] << " \n"[i == c.back()];
return;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--)
{
solve();
}
return 0;
}
B Olya and Game with Arrays
最小的 + 选 n-1 组中的次小值
- 如果最小的不动,那么就是最小的加上其他所有组的次小值
- 如果最小的动,这组变成加次小值,最小的移动到的组变成加最小值
两种情况是等价的
比赛的时候我是暴力枚举把哪 n-1 行的最小值扔到 另外一行 在加上这一行(包括被扔过来的)的最小值
// 赛时代码
#include <bits/stdc++.h>
#define LL long long
#define ULL unsigned long long
#define x first
#define y second
#define endl "\n"
using namespace std;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int INF = 0x3f3f3f3f;
void solve()
{
int n, m;
cin >> n;
vector<int> vm1(n + 1), vm2(n + 1);
vector<vector<int>> a(n + 1);
for (int i = 1; i <= n; i++)
{
cin >> m;
int minv1 = INF, minv2 = INF;
a[i].resize(m + 1);
for (int j = 1; j <= m; j++)
{
cin >> a[i][j];
if (a[i][j] < minv1)
{
minv2 = minv1;
minv1 = a[i][j];
}
else if (a[i][j] < minv2)
{
minv2 = a[i][j];
}
}
vm1[i] = minv1;
vm2[i] = minv2;
}
LL ans = 0;
for (int i = 1; i <= n; i++)
{
LL res = 0;
int minv = vm1[i];
for (int j = 1; j <= n; j++)
if (i != j)
{
res = res + (LL)vm2[j];
minv = min(minv, vm1[j]);
}
res = res + (LL)minv;
ans = max(ans, res);
}
cout << ans << endl;
// for (int i = 1; i <= n; i++)
// cerr << i << ' ' << vm1[i] << ' ' << vm2[i] << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--)
{
solve();
}
return 0;
}
from jiangly
#include <bits/stdc++.h>
#define LL long long
#define ULL unsigned long long
#define x first
#define y second
#define endl "\n"
using namespace std;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int INF = 0x3f3f3f3f;
void solve()
{
int n;
cin >> n;
vector<int> a;
int min1 = 1E9;
int min2 = 1E9;
LL ans = 0;
for (int i = 0; i < n; i++)
{
int m;
cin >> m;
a.resize(m);
for (int j = 0; j < m; j++)
{
cin >> a[j];
}
nth_element(a.begin(), a.begin() + 1, a.end());
min1 = min(min1, a[0]);
min2 = min(min2, a[1]);
ans += a[1];
}
ans -= min2;
ans += min1;
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--)
{
solve();
}
return 0;
}
Trick
nth_element(a.begin(), a.begin() + x, a.end())
作用是将第(x + 1)小的数放在a[x]上
并且满足
a[0] ~ a[x - 1] <= a[x] <= a[x] ~ a[n - 1]
所以
nth_element(a.begin(), a.begin() + 1, a.end())
将第二小的数放到了a[1]
进而a[0]只能为最小的数
C Another Permutation Problem
求n的全排列中,以下式子的最大值
\((\sum_{i=1}^{n}p_{i}*i)-(max_{j=1}^{n}p_{j}*j)\)
法一:打表
发现最优解是将某个后缀翻转,形如{1, 2, k,...,n, n - 1,...,k + 1}
枚举翻转长度即可
#include <bits/stdc++.h>
#define LL long long
#define ULL unsigned long long
#define x first
#define y second
#define endl "\n"
using namespace std;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int INF = 0x3f3f3f3f;
void solve()
{
int n;
cin >> n;
LL ans = 0;
for (int i = 0; i <= n; i++)
{
LL res = 0;
LL maxv = i * i;
for (int j = 1; j <= i; j++)
{
res += (LL)j * j;
}
maxv = i * i;
for (int j = i + 1, k = n; j <= n; j++, k--)
{
res += (LL)j * k;
maxv = max(maxv, (LL)j * k);
}
res -= maxv;
ans = max(ans, res);
}
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--)
{
solve();
}
return 0;
}
法二:from jiangly
暴力 and 并查集
枚举\((max_{j=1}^{n}p_{j}*j)\)的值,进行限制。
之后从第n位开始填,尽可能填大的数。
并查集的用于维护的是,如果我现在这个位置上可以填的最大的数是x,但是x已经被填过了。
因为在x填的时候让p[x]=x-1,所以find(x)会从x向更小的数找x-1,x-2...直到第一个没有填的数
#include <bits/stdc++.h>
#define LL long long
#define ULL unsigned long long
#define x first
#define y second
#define endl "\n"
using namespace std;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int INF = 0x3f3f3f3f;
struct DSU
{
vector<int> f, siz;
DSU() {}
DSU(int n)
{
init(n);
}
void init(int n)
{
f.resize(n);
iota(f.begin(), f.end(), 0);
siz.assign(n, 1);
}
int find(int x)
{
while (x != f[x])
{
x = f[x] = f[f[x]];
}
return x;
}
bool same(int x, int y)
{
return find(x) == find(y);
}
bool merge(int x, int y)
{
x = find(x);
y = find(y);
if (x == y)
{
return false;
}
siz[x] += siz[y];
f[y] = x;
return true;
}
int size(int x)
{
return siz[find(x)];
}
};
void solve()
{
int n;
cin >> n;
int ans = 0;
for (int l = n * n;; l--)
{
DSU dsu(n + 1);
int sum = 0;
bool ok = true;
for (int i = n; i >= 1; i--)
{
int x = dsu.find(min(n, l / i));
if (x == 0)
{
// 表示已经开始无法满足了
ok = false;
break;
}
sum += i * x;
dsu.merge(x - 1, x); // p[x] = x - 1
}
if (!ok)
{
break;
}
ans = max(ans, sum - l);
}
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--)
{
solve();
}
return 0;
}
D Andrey and Escape from Capygrad
发现对于 l r a b
l到b的数都会跳到b,b到r的数不会动
故a和r是两个没有用的数,将所有线段的[l, b],区间合并即可
之后二分找第一个大于x的r即可
from jiangly
#include <bits/stdc++.h>
#define LL long long
#define ULL unsigned long long
#define x first
#define y second
#define endl "\n"
using namespace std;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int INF = 0x3f3f3f3f;
void solve()
{
int n;
cin >> n;
vector<array<int, 2>> seg(n);
for (int i = 0; i < n; i++)
{
int l, r, a, b;
cin >> l >> r >> a >> b;
seg[i] = {l, b};
}
sort(seg.begin(), seg.end());
vector<array<int, 2>> a;
for (auto [l, r] : seg)
{
if (!a.empty() && l <= a.back()[1])
{
a.back()[1] = max(a.back()[1], r);
}
else
{
a.push_back({l, r});
}
}
int q;
cin >> q;
for (int i = 0; i < q; i++)
{
int x;
cin >> x;
int j = lower_bound(a.begin(), a.end(), array{x + 1, 0}) - a.begin() - 1;
if (j >= 0)
{
x = max(x, a[j][1]);
}
cout << x << " \n"[i == q - 1];
}
return;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--)
{
solve();
}
return 0;
}
E Maximum Monogonosity
DP
题目:
给两个长度均为 n 的数组 a, b
对选取 [l, r] 一段
长度为 \(r-l+1\)
价值为 \(\lvert a_l - b_r \rvert + \lvert a_r - b_l \rvert\)
选取总长度为 k 的段,每个段都互不相交,求最大总价值
法一:
暴力dp + 剪枝
\(dp[i][j]\) 表示的是前i个字符,还需要总长度为j的片段的最大价值
第一个循环:i
枚举到第i个点
第二个循环:len
枚举最后一段的长度,即枚举是从哪个点转移过来
最后一段即为 [i-len+1, i]
**第三个循环: **z
枚举除去这段前,仍需要的段的总长度
for (int i = 1; i <= n; i++)
{
for (int len = 1; len <= i; len++)
{
int st = i - len + 1;
for (int z = len; z <= k; z++)
{
dp[i][z - j] = max(dp[i][z - j], dp[st - 1][z] + + (LL)abs(a[st] - b[i]) + abs(a[i] - b[st])));
}
}
// 因为dp[i][j] 表示的是前i个字符,还需要总长度为j的片段
// 而每次上面的循环计算出来的dp[i][j]都是i这个点在目前的最后一段里的
// 所以需要把i这个点不在目前的最后一段里的也max以下
// 这个值就是dp[i - 1][j]
for (int j = 0; j <= k; j++)
{
dp[i][j] = max(dp[i - 1][j], dp[i][j]);
}
}
#include <bits/stdc++.h>
#define LL long long
#define ULL unsigned long long
#define x first
#define y second
#define endl "\n"
using namespace std;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int INF = 0x3f3f3f3f;
void solve()
{
int n, m;
cin >> n >> m;
vector<int> a(n + 1), b(n + 1);
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++)
cin >> b[i];
vector<vector<LL>> dp(n + 1, vector<LL>(m + 1));
for (int i = 1; i <= n; i++)
{
for (int len = 1; len <= i; len++)
{
int st = i - len + 1;
for (int z = max(len, m - (st - 1)); z <= min(m, n - (st - 1)); z++)
{
dp[i][z - len] = max(dp[i][z - len], dp[st - 1][z] + (LL)abs(a[st] - b[i]) + abs(a[i] - b[st]));
}
}
for (int j = 0; j <= m; j++)
{
dp[i][j] = max(dp[i - 1][j], dp[i][j]);
}
}
LL ans = 0;
for(int i = 0; i <= n; i ++)
ans = max(ans, dp[i][0]);
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--)
{
solve();
}
return 0;
}
法二:
标准解法
参考
https://www.cnblogs.com/szdytom/p/how-did-we-get-here-2.html
https://zhuanlan.zhihu.com/p/649682355
首先拆开绝对值
将对于\(\lvert a_l - b_r \rvert + \lvert a_r - b_l \rvert\)的维护转化成
对于\(/+a_l + b_l/+a_l - b_l/-a_l + b_l/-a_l - b_l/\)这四个值的维护
同时维护两个数组dp mx
mx数组更新的是选择第一个点,即每一段的左端点
dp数组更新的是选择第二个点,即每一段的右端点
\(dp[i][j]\):表示前i个点,选的段的总长度为j
\(mx[u][id]\):u表示是拆分出来的四个式子中的哪一个,id表示i-j的值为多少
因为每次选一段 [l, r] 长度 len 后
i变成ii=i+len,j变成jj=j+len,会发现一件神奇的事情是i-j==ii-jj
所以每次转移的时候i-j的值是固定的
可以按照i-j的大小进行分类转移
#include <bits/stdc++.h>
#define LL long long
#define ULL unsigned long long
#define x first
#define y second
#define endl "\n"
using namespace std;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int N = 3010;
const LL INF = 1e18;
LL mx[4][N];
LL dp[N][N];
int s1[4] = {1, 1, -1, -1};
int s2[4] = {1, -1, 1, -1};
void solve()
{
int n, k;
cin >> n >> k;
vector<int> a(n + 2), b(n + 2);
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++)
cin >> b[i];
auto get = [&](int x, int id, int u) -> LL
{
return dp[x][x - id] + a[x + 1] * s1[u] + b[x + 1] * s2[u];
};
dp[0][0] = 0;
for (int i = 1; i <= k; i++)
dp[0][i] = -INF;
for (int i = 0; i < 4; i++)
{
for (int j = 0; j <= n; j++)
{
mx[i][j] = -INF;
}
}
for (int u = 0; u < 4; u++)
{
mx[u][0] = get(0, 0, u);
}
for (int i = 1; i <= n; i++)
{
for (int j = 0; j <= k; j++)
{
dp[i][j] = dp[i - 1][j];
}
for (int j = 0; j <= min(k, i); j++)
{
int id = i - j;
for (int u = 0; u < 4; u++)
{
dp[i][j] = max(dp[i][j], mx[u][id] - s1[u] * b[i] - s2[u] * a[i]);
}
for (int u = 0; u < 4; u++)
{
mx[u][id] = max(mx[u][id], get(i, id, u));
}
}
}
cout << dp[n][k] << '\n';
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--)
{
solve();
}
return 0;
}