牛客20220723第二场
Link with Monotonic Subsequence
题目描述:
构造长度为 n 的排列 p,使得 max {lis(p), lds(p)} 最小
lis(p): p 的最长上升子序列的长度
lds(p): p 的最长下降子序列的长度
解:
max {lis(p), lds(p)} 的下界是多少?
如果 n = k,构造(n − k + 1 n − k + 2 ... n)...(k + 1 k + 2 ... 2k)(1 2 ... k)
证明: lds(p) = 最小上升子序列分划数 (Dilworth 定理)
⇒ lis(p) lds(p) ≥ n ⇒ max {lds(p), lis(p)} ≥
Link with Arithmetic Progression
题目描述:
给定一串整数a1、a2、a3、a4、an,将它调成等差数列b1、b2、b3、b4、bn
使得 最小
解:
这题本质上是线性回归的题目,有两种做法
1、直接套线性回归公式算出拟合直线
2、先算出x,y的平均值,根据线性回归的性质,可以知道这个点必在直线上,接着就可以利用三分法枚举斜率,同样可以计算出结果
方法1:直接套公式
#include<iostream> #include <cstdio> #include <cctype> #include <iomanip> #include<bits/stdc++.h> using namespace std; typedef long long ll; 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; ll arr[100005]; void solve() { ll n = gti(); long double xy = 0, xx = 0, x_p = 0, y_p = 0; for (ll i = 1; i <= n; i++) { arr[i] = gti(); xy += i * arr[i]; xx += i * i; x_p += i; y_p += arr[i]; } x_p = x_p / n; y_p = y_p / n; long double b = (xy - n * x_p * y_p) / (xx - n * x_p * x_p); long double a = y_p - b * x_p; long double res = 0; for (ll i = 1; i <= n; i++) { res += (b * i + a - arr[i]) * (b * i + a - arr[i]); } cout << fixed << setprecision(15) << res << endl; } int main() { int t = gti(); while (t--) { solve(); } }
方法二:三分枚举斜率
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef long double ld; ll y[100005]; ll n; ld y_p, x_p; #include <cstdio> #include <cctype> 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; ld dis(ld k) { ld res = 0; for (ll i = 1; i <= n; i++) { res += pow((k * (i - x_p) + y_p - y[i]), 2); } return res; } void solve() { n = gti(); ll yy = 0; for (ll i = 1; i <= n; i++) { y[i] = gti(); yy += y[i]; } y_p = (ld) yy / n; x_p = (ld) (1 + n) / 2; ld r = 1e9, l = -1e9, k1, k2, k, res; while (r - l > 1e-9) { k = (r - l) / 3; k1 = l + k; k2 = l + k + k; ld resk1 = dis(k1); ld resk2 = dis(k2); res = resk1 < resk2 ? resk1 : resk2; res=min(res,dis((k1+k2)/2)); if (resk1 < resk2) { r = k2; } else { l = k1; } } cout << fixed << setprecision(15) << res << endl; } int main() { int t = gti(); while (t--) { solve(); } }
Link with Game Glitch
题目描述:
有n个物品,给出m种合成方式,每种合成方式都是用a个b物品能合成c个d物品,所以可能有某种方式能够得到无限大的某种物品,为了避免这个问题,现在添加一个参数w,只能用a个b物品合成w*c个d物品,求出满足题意的最大的w
解:
建图,n个顶点,m条边,每条边的权值就是c/a,二分枚举w,每条边的权值就变成了w*c/a,如果存在一个边权乘积大于1的环,就可以无限得到物品,对每条边取-log,就变成了如果边权相加存在负环,那么就可以无限得到物品,这样就可以求出答案。
#include<iostream> #include<vector> #include<queue> #include<cmath> #include <iomanip> using namespace std; typedef long long ll; struct edge { int to; double weight; }; vector<edge> v[1005]; int times[1005]; double dis[1005]; bool contain[1005]; bool view[1005]; int n, m; double w; bool isloop() { queue<int> q; for (int i = 1; i <= n; i++) { dis[i] = 1e100; times[i] = 0; contain[i] = false; view[i] = false; } int cur; for (int i = 1; i <= n; i++) { if (view[i]) { continue; } else { q.push(i); dis[i] = 0; times[i] = 1; contain[i] = true; while (!q.empty()) { cur = q.front(); q.pop(); view[i] = true; contain[cur] = false; for (edge e: v[cur]) { int to = e.to; double wei = -log(e.weight * w); if (dis[to] > dis[cur] + wei) { dis[to] = dis[cur] + wei; if (!contain[to]) { times[to]++; if (times[to] >= n) { return true; } q.push(to); contain[to] = true; } } } } } } return false; } int main() { cin >> n >> m; int a, b, c, d; //建图 for (int i = 0; i < m; i++) { cin >> a >> b >> c >> d; edge e = {d, c * 1.0 / a}; v[b].push_back(e); } double l = 0, r = 1, res = 0; while (r - l > 1e-9) { w = (l + r) / 2; if (!isloop()) { l = w; res = w; } else { r = w; } } cout << fixed << setprecision(10) << res << endl; }
Link with Bracket Sequence I
题目描述:
已知括号序列 a 是一个长度为 m 的合法括号序列 b 的子序列,求可能的序列 b 的数量。
解:
dp[i][j][k]表示a的前i个,与b的最大公共子序列为j,此时左括号比右括号多k个
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef long double ld; const ll mod = 1e9 + 7; int n, m; ll f[205][205][205]; char chars[205]; void add(ll &a, ll b) { a = (a + b) % mod; } void solve() { scanf("%d%d\n%s", &n, &m, chars + 1); //归零 for (int i = 0; i <= m; i++) { for (int j = 0; j <= n; j++) { for (int k = 0; k <= m; k++) { f[i][j][k] = 0; } } } //b的前0个中,与a的最长公共子序列为0,左括号比右括号多0的情况为1,即空串为1. f[0][0][0] = 1; for (int i = 0; i <= m; i++) {//表示b的前i个字符 for (int j = 0; j <= n; j++) {//表示b与a的最长公共子序列 for (int k = 0; k <= i; k++) {//表示左括号比右括号多的个数 if (k >= 1) {//如果左括号比右括号多,那么a的下一个位置放右括号 if (j + 1 <= n && chars[j + 1] == ')') {//如果在下一个放右括号,并且b在这个位置的下一个也是右括号,那么最长公共子序列就会加一 add(f[i + 1][j + 1][k - 1], f[i][j][k]); } else { add(f[i + 1][j][k - 1], f[i][j][k]); } } //左右括号一样多,我们放左括号 if (j + 1 <= n && chars[j + 1] == '(') { add(f[i + 1][j + 1][k + 1], f[i][j][k]); } else { add(f[i + 1][j][k + 1], f[i][j][k]); } } } } cout << f[m][n][0] << endl; } int main() { int t; cin >> t; while (t--) { solve(); } }#ACM##算法##来新手村#