"蔚来杯"2022牛客暑期多校训练营2
G Link with Monotonic Subsequence
题目大意
构造一个排列,使其 max(lis§, lds§) 最小,其中lis是最长上升子序列长度,lds是最长下降子序列长度。
题解
结论:排列权值的最小值为 ⌈√n⌉。
可以把n个数字分成⌈√n⌉组,如果n = 9,则为789、456、123,显然每组里面递增,各个组之间递减。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;
int n, t;
int a[maxn];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> t;
while (t--)
{
cin >> n;
int num = ceil(sqrt(n));
int head = num * num - (num - 1);
int cnt = 0;
for (int i = 0; i < num; i++)
{
for (int j = 0; j < num; j++)
{
if (head + j > n)
continue;
a[cnt++] = head + j;
}
head -= num;
}
cout << a[0];
for (int i = 1; i < cnt; i++)
cout << " " << a[i];
cout << endl;
}
}
J Link with Arithmetic Progression
题目大意
有一个数列 a ,将其修改为一个等差数列 b,代价为 ∑ (ai − bi)2,求最小代价。
题解
该题使用线性回归,直接套式子即可。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
typedef long double ld;
int t, n;
ld a[maxn];
ld sx, sy, sxx, sxy, A, B;
double res;
namespace GTI
{
char gc(void)
{
const int S = 1 << 16;
static char buf[S], *s = buf, *t = buf;
if (s == t)
t = buf + fread(s = buf, 1, S, stdin);
if (s == t)
return EOF;
return *s++;
}
int gti(void)
{
int a = 0, b = 1, c = gc();
for (; !isdigit(c); c = gc())
b ^= (c == '-');
for (; isdigit(c); c = gc())
a = a * 10 + c - '0';
return b ? a : -a;
}
}
using GTI::gti;
int main()
{
t = gti();
while (t--)
{
sx = sy = sxx = sxy = B = A = res = 0;
n = gti();
for (int i = 1; i <= n; i++)
{
a[i] = gti();
sy += a[i];
sx += (ld)i;
sxy += (ld)i * a[i];
sxx += (ld)i * (ld)i;
}
B = ((ld)n * sxy - sx * sy) / ((ld)n * sxx - sx * sx);
A = (sy * sxx - sx * sxy) / (n * sxx - sx * sx);
for (int i = 1; i <= n; i++)
res += (a[i] - A - B * (ld)i) * (a[i] - B * (ld)i - A);
cout << fixed << setprecision(15) << res << endl;
}
}
K Link with Bracket Sequence I
题目大意
已知括号序列 a 长度为n,且 a 是一个长度为 m 的合法括号序列 b 的子序列,求可能的序列 b 的数量。
题解
该题使用dp,记 dp(i,j,k)表示在序列 b 的前 i 位中,与 a 的 lcs 为 j ,且左括号比右括号多 k 个的方案数。
初始化:dp [0] [0] [0]=1;dp [i] [0] [j] += (j - 1 < 0 ? 0 : dp [i - 1] [0] [j - 1]) + dp [i - 1] [0] [j + 1]。
递推公式:每次枚举b中第i个字符的可能情况,以及其是否与序列a的第j个字符匹配,所以一共有四种情况,可以根据a[j]的字符分为两类。
当a[j]是左括号时,
①b[i]是左括号,所以dp [i] [j] [k] = (dp [i] [j] [k] + dp[i - 1] [j - 1] [k - 1]) % mod;
②b[i]是右括号,所以dp [i] [j] [k] = (dp [i] [j] [k] + dp [i - 1] [j] [k + 1]) % mod;
当a[j]是右括号时,
①b[i]是右括号,所以dp [i] [j] [k] = (dp [i] [j] [k] + dp [i - 1] [j - 1] [k + 1]) % mod;
②b[i]是左括号,所以dp [i] [j] [k] = (dp [i] [j] [k] + dp [i - 1] [j] [k - 1]) % mod;
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e2 + 5;
const int mod = 1e9 + 7;
int t, n, m;
char a[maxn];
int dp[maxn][maxn][maxn];
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> t;
while (t--)
{
memset(dp, 0, sizeof dp);
cin >> n >> m >> a + 1;
dp[0][0][0] = 1;
for (int i = 1; i <= m; i++)
{
for (int j = 0; j <= i; j++)
{
dp[i][0][j] += (j - 1 < 0 ? 0 : dp[i - 1][0][j - 1]) + dp[i - 1][0][j + 1];
dp[i][0][j] %= mod;
}
}
for (int i = 1; i <= m; i++)
{
for (int j = 1; j <= min(i, n); j++)
{
for (int k = 0; k <= i; k++)
{
if (a[j] == '(')
{
dp[i][j][k] = (dp[i][j][k] + dp[i - 1][j - 1][k - 1]) % mod;
dp[i][j][k] = (dp[i][j][k] + dp[i - 1][j][k + 1]) % mod;
}
if (a[j] == ')')
{
dp[i][j][k] = (dp[i][j][k] + dp[i - 1][j - 1][k + 1]) % mod;
dp[i][j][k] = (dp[i][j][k] + dp[i - 1][j][k - 1]) % mod;
}
}
}
}
cout << dp[m][n][0] << endl;
}
return 0;
}
D Link with Game Glitch
题目大意
给定 m 个物品合成的方式,即用 ai 个 bi 物品可以合成 ci 个 di 物品,求一个最大的合成损耗参数 w ,使得所有物品都无法通过无限合成的方式无限获得。
题解
建图,对于每个物品建点,每个合成方式由 bi 向 di 建有向边,边权为 ci/ai 。
该题实际上是要求一个最大的 w ,使得在每条边的边权乘上 w 之后,不存在一个乘积大于 1 的环。
可以二分答案,check 的问题类似于求负环。另外,由于边权乘积较大,需要对其取对数。
取对数处理:假如某个环的边权为a、b、c、d,那么要保证abcd≤1,两边取对数,log a + log b + log c + log d ≤ 0,即- log a - log b - log c - log d ≥ 0。
所以如果建图时存入的边权为-log ci/ai,那么即为判断是否存在负环。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 5;
const double eps = 1e-10;
int n, m, ans;
double dis[maxn];
int cnt[maxn];
bool vis[maxn];
struct Edge
{
int v;
double w;
Edge(int v = 0, double w = 0) : v(v), w(w) {}
};
vector<Edge> e[maxn];
bool checked(double mid)
{
memset(dis, 0, sizeof dis);
memset(vis, 0, sizeof vis);
memset(cnt, 0, sizeof cnt);
queue<int> q;
for (int i = 1; i <= n; i++)
q.push(i), vis[i] = 1;
while (!q.empty())
{
int now = q.front();
q.pop();
vis[now] = 0;
for (int i = 0; i < e[now].size(); i++)
{
int v = e[now][i].v;
double w = e[now][i].w;
if (dis[v] > dis[now] + w + mid)
{
dis[v] = dis[now] + w + mid;
if (++cnt[v] == n + 5)
return 0;
if (!vis[v])
{
q.push(v);
vis[v] = 1;
}
}
}
}
return 1;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
int b, d;
double a, c;
cin >> a >> b >> c >> d;
e[b].emplace_back(d, log(a) - log(c));
}
double l = 0, r = 1, mid;
while (abs(r - l) >= eps)
{
mid = (l + r) / 2;
ans++;
if (checked(-log(mid)))
l = mid;
else
r = mid;
}
cout << fixed << setprecision(10) << r << endl;
return 0;
}
H Take the Elevator
题目大意
n 个人坐电梯,楼高 m ,每个人有起始楼层和目标楼层。电梯有载客量限制 k ,上升时可以上升到任意层并随时下降,但是下降要一直下降到一层才能再上升。电梯每秒运行一层,换方向和上下人不占用时间,问电梯最短运行时间。
题解
代码
未完