NOIP13连测 #1
A
贪心!
-
40pts
一一枚举任意两个点,进行最大值的求解。 -
+40pts
假设两个点的横坐标和纵坐标之差分别为 \(x,y\),问题等价于最大化 $$ \frac{x+y}{\sqrt{x2+y2}} $$ 容易发现在 \(x=y\) 时取到最大值 \(\sqrt 2\)。
当 \(x_i+y_i=x_j+y_j\) 时,\(x_i-x_j=y_j-y_i\),两个点的横纵坐标之差恰好相同,所以答案为 \(\sqrt 2\)。
当 \(x_i-y_i=x_j-y_j\) 时,\(x_i-x_j=y_i-y_j\),两个点的横纵坐标之差恰好相同,所以答案为 \(\sqrt 2\)。
- 100pts
给出结论,将所有点按 \(x_i+y_i\) 排好序或按 \(x_i-y_i\) 排好序,把任意两个相邻节点的答案取最大值,即为最终答案。
以下给出具体证明。
根据部分分的启发,容易发现两点连线和 \(x\) 轴的坐标越靠近 \(45°\) 或 \(135°\) 答案越优。
我们把整个坐标轴旋转一下,原本点 \((x,y)\) 变成 \(\frac{x+y}{2},\frac{x-y}{2}\),即以 \(y=x\) 和 \(y=-x\) 两条线作为旋转后的坐标轴。
那么问题就变成了找到两个点,它们和 \(x\) 轴或者 \(y\) 轴的夹角尽量小,可以证明,一定满足一对点,它们按 \(x\) 排序或按 \(y\) 排序是相邻 的,所以问题得证。
#include <bits/stdc++.h>
using namespace std;
#define FOR(i, a, b) for (int i = (a); i <= (b); ++i)
#define ROF(i, a, b) for (int i = (a); i >= (b); --i)
#define DEBUG(x) cerr << #x << " = " << x << endl
#define ll long long
typedef pair <int, int> PII;
typedef unsigned int uint;
typedef unsigned long long ull;
#define i128 __int128
#define fi first
#define se second
mt19937 rnd(chrono::system_clock::now().time_since_epoch().count());
#define ClockA clock_t start, end; start = clock()
#define ClockB end = clock(); cerr << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl;
//#define int long long
inline int rd(){int x = 0, f = 1;char ch = getchar();while (ch < '0' || ch > '9'){if (ch == '-')f = -1;ch = getchar();}while (ch >= '0' && ch <= '9')x = (x << 3) + (x << 1) + ch - '0', ch = getchar();return x * f;
}
#define rd rd()void wt(int x){if (x < 0)putchar('-'), x = -x;if (x > 9)wt(x / 10);putchar(x % 10 + '0');return;
}
void wt(char x){putchar(x);
}
void wt(int x, char k){wt(x),putchar(k);
}namespace Star_F{const int N = 100005;int n;PII a[N], p[N], q[N];double dis1(int P, int Q) { return abs(a[P].fi - a[Q].fi) + abs(a[P].se - a[Q].se); }double dis2(int P, int Q) {return sqrt(1ll * (a[P].fi - a[Q].fi) * (a[P].fi - a[Q].fi) + 1ll * (a[P].se - a[Q].se) * (a[P].se - a[Q].se));}double ans = -1;void Main(){cin >> n;for (int i = 1; i <= n;i++)cin >> a[i].fi >> a[i].se;for (int i = 1; i <= n;i++){p[i] = {a[i].fi - a[i].se, i};q[i] = {a[i].fi + a[i].se, i};}sort(p + 1, p + n + 1, [](PII a, PII b){ return a.fi < b.fi; });sort(q + 1, q + n + 1, [](PII a, PII b){ return a.fi < b.fi; });for (int i = 2; i <= n;i++){double x = dis1(p[i - 1].se, p[i].se) / dis2(p[i - 1].se, p[i].se);double y = dis1(q[i - 1].se, q[i].se) / dis2(q[i - 1].se, q[i].se);ans = max({ans, x, y});}printf("%.15lf\n", ans);ans=-1;}}signed main(){freopen("Apair.in","r",stdin);freopen("Apair.out","w",stdout);ClockA;int T=1;T=rd;while(T--) Star_F::Main();// ClockB;return 0;
}/*
* ▀▀▀██████▄▄▄ _______________
* ▄▄▄▄▄ █████████▄ / \
* ▀▀▀▀█████▌ ▀▐▄ ▀▐█ | Code has no BUG! |
* ▀▀█████▄▄ ▀██████▄██ | _________________/
* ▀▄▄▄▄▄ ▀▀█▄▀█════█▀ |/
* ▀▀▀▄ ▀▀███ ▀ ▄▄
* ▄███▀▀██▄████████▄ ▄▀▀▀▀▀▀█▌ ______________________________
* ██▀▄▄▄██▀▄███▀ ▀▀████ ▄██ █ \\
* ▄▀▀▀▄██▄▀▀▌████▒▒▒▒▒▒███ ▌▄▄▀▀▀▀█_____________________________ //
* ▌ ▐▀████▐███▒▒▒▒▒▐██▌
* ▀▄▄▄▄▀ ▀▀████▒▒▒▒▄██▀
* ▀▀█████████▀
* ▄▄██▀██████▀█
* ▄██▀ ▀▀▀ █
* ▄█ ▐▌
* ▄▄▄▄█▌ ▀█▄▄▄▄▀▀▄
* ▌ ▐ ▀▀▄▄▄▀
* ▀▀▄▄▀ ██
* \ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀
* \- ▌ Name: Star_F ▀ ▀
* - ▌ (o) ▀
* /- ▌ Go Go Go ! ▀ ▀
* / ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀
*/
B
主要考察了构造的思想以及数论的相关内容。
30pts
通过搜索进行暴力求解。
+10pts
任意发现 \(n\) 为偶数时需要奇数条边,每次加两条边不可能满足条件,所以无解。
+30pts
\(\gcd(n,k)=1\) 时,容易发现 \(1,1+k,1+2k,1+3k,…,1+(n-1)k\) 这些数两两不同。
那么按照这个顺序,第一次操作取 \((1,1+k)\),第二次操作取 \((1+3k,1+4k)\) 以此类推,即可连出一条链。
100pts
我们把所有数按如下方式写出来。
其中矩阵的列为 \(\gcd(n,k)\)。
以 \(n=25,k=5\) 为例
因为 \(n\) 为奇数,所以这个矩形的行和列一定都是奇数。
我们可以把每个点都和它右下角的那个点连接,例如操作 \((0,6)\) 可以同时把 \((0,6),(5,11)\) 连接,操作 \((10,16)\) 可以同时把 \((10,16),(15,21)\) 连接,因为每行每列都是奇数,所以这个操作一定是可行的。
现在每条从左上到右下的斜线都已经联通了,我们需要把剩下的这些线连接在一起形成树。
注意到我们每次选择一个点和它正下方的那个点,把任意两条相邻的斜线连接,例如 \((0,6,12,18,24),(5,11,17,23),(10,16,22)\) 这三条直线可以通过操作 \((5,6)\) 操作直接相连。
那么按照这个顺序把所有相邻的斜线全部连接即可。
时间复杂度为 \(O(n)\)。
#include <bits/stdc++.h>
using namespace std;
#define FOR(i, a, b) for (int i = (a); i <= (b); ++i)
#define ROF(i, a, b) for (int i = (a); i >= (b); --i)
#define DEBUG(x) cerr << #x << " = " << x << endl
#define ll long long
typedef pair <int, int> PII;
typedef unsigned int uint;
typedef unsigned long long ull;
#define i128 __int128
#define fi first
#define se second
mt19937 rnd(chrono::system_clock::now().time_since_epoch().count());
#define ClockA clock_t start, end; start = clock()
#define ClockB end = clock(); cerr << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl;
//#define int long long
inline int rd(){int x = 0, f = 1;char ch = getchar();while (ch < '0' || ch > '9'){if (ch == '-')f = -1;ch = getchar();}while (ch >= '0' && ch <= '9')x = (x << 3) + (x << 1) + ch - '0', ch = getchar();return x * f;
}
#define rd rd()void wt(int x){if (x < 0)putchar('-'), x = -x;if (x > 9)wt(x / 10);putchar(x % 10 + '0');return;
}
void wt(char x){putchar(x);
}
void wt(int x, char k){wt(x),putchar(k);
}namespace Star_F{int n, k, ne[2000005];void Main(){cin >> n >> k;if(!(n&1)){cout << -1 << endl;return;}cout << n / 2 << endl;int i, j, len = 1, tot, p = k, x, y;while(p!=0)p = (p + k) % n, len++;tot = n / len;for (i = 0; i < n;i++)ne[i] = (i + k) % n;if (tot == 1){for (i = 1, x = 0; i < len; i += 2, x = ne[ne[x]])cout << x << " " << ne[x] << endl;return;}for (i = 0; i < tot - 1;i++)for (j = 2,x = i, y = ne[i + 1]; j < len; j += 2, x = ne[ne[x]], y = ne[ne[y]])cout << x << " " << y << endl;for (i = 2, x = ne[0], y = ne[1]; i <= len; i += 2, x = ne[ne[x]], y = ne[ne[y]])cout << x << " " << y << endl;for (i = 1; i < tot; i += 2)cout << i << " " << i + 1 << endl;}}signed main(){freopen("Btree.in","r",stdin);freopen("Btree.out","w",stdout);ClockA;int T=1;// T=rd;while(T--) Star_F::Main();// ClockB;return 0;
}/*
* ▀▀▀██████▄▄▄ _______________
* ▄▄▄▄▄ █████████▄ / \
* ▀▀▀▀█████▌ ▀▐▄ ▀▐█ | Code has no BUG! |
* ▀▀█████▄▄ ▀██████▄██ | _________________/
* ▀▄▄▄▄▄ ▀▀█▄▀█════█▀ |/
* ▀▀▀▄ ▀▀███ ▀ ▄▄
* ▄███▀▀██▄████████▄ ▄▀▀▀▀▀▀█▌ ______________________________
* ██▀▄▄▄██▀▄███▀ ▀▀████ ▄██ █ \\
* ▄▀▀▀▄██▄▀▀▌████▒▒▒▒▒▒███ ▌▄▄▀▀▀▀█_____________________________ //
* ▌ ▐▀████▐███▒▒▒▒▒▐██▌
* ▀▄▄▄▄▀ ▀▀████▒▒▒▒▄██▀
* ▀▀█████████▀
* ▄▄██▀██████▀█
* ▄██▀ ▀▀▀ █
* ▄█ ▐▌
* ▄▄▄▄█▌ ▀█▄▄▄▄▀▀▄
* ▌ ▐ ▀▀▄▄▄▀
* ▀▀▄▄▀ ██
* \ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀
* \- ▌ Name: Star_F ▀ ▀
* - ▌ (o) ▀
* /- ▌ Go Go Go ! ▀ ▀
* / ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀
*/
C
10pts
直接输出 \(p\) 即可。
20pts
通过爆搜,每次暴力枚举括号插入位置。
40pts
通过状态压缩DP,记录插入 \(k\) 次操作以后,所有括号序列得到的概率,时间复杂度为 \(O(n \times 2^{2n})\),实际复杂度远小于这个值,因为并不是所有括号匹配都能够最终得到。
70pts
容易发现,括号序列一共有 \(1⋅3⋅5...⋅(2𝑛−1)\) 种生成方式。
假如 (
为 \(1\),)
为 \(−1\),那么一个序列合法的充要条件为:最小前缀和为 \(0\),且以 \(0\) 结尾。
现在考虑维护这些前缀和。
如果我们在当前序列某一位后插入一个 ()
,且那一位的前缀和为 \(𝑥\),那么相当于把 \(𝑥\) 替换成 \([𝑥,𝑥+1,𝑥]\)。
同理可知,插入 )(
相当于把 \(𝑥\) 替换成 \([𝑥,𝑥−1,𝑥]\)。
我们观察到每次选中的数概率是均等的,所以我们并不关心数的顺序,只关心这个前缀和数组中每个数出现了多少次。
定义 \(𝑓_{𝑛,𝑥}\) 为前缀和数组一开始只有一个数,这个数为 \(x\),执行 \(𝑛\) 次操作以后得到的前缀和数组中每个元素均不小于 \(0\) 的概率。
有两种转移,第一种下一步变成了 \(x,x+1,x\),那么概率为
第一种下一步变成了 \(x,x-1,x\),那么概率为
最终答案即为 \(f_{0,n}\),时间复杂度为 \(O(n^4)\)。
100pts
只需要将上面的 DP 过程优化即可,优化的思路很多,以下给出一种。
以第一种转移为例,引入辅助DP数组 \(g\)。
\(g\) 的计算是 \(O(n^3)\) 的,再通过 \(g\) 计算第一种情况:
相当于对于 \(i,j,k\) 三维不一起枚举,先合并 \(i,j\),再合并 \(j,k\),时间复杂度为 \(O(n^3)\)。
#include <iostream>
#include <cstdio>
using namespace std;
#define int long long
const int N= 510;
const int mod = 998244353;
int n, X, Y, p, c[N[N], f[N][N], g[N][N];
inline int qpow(int x, int y) {int res = 1;for (; y; y >>= 1, x = x * x % mod)if (y & 1)res = res * x % mod;return res;
}
void init_C() {for (int i = 0; i <= n; i++) c[i][0] = 1;for (int i = 1; i <= n; i++)for (int j = 1; j <= i; j++) c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
}
void DP() {for (int i = 0; i <= n; i++) f[0][i] = g[0][i] = 1;for (int i = 1; i <= n; i++) {for (int x = 0; x <= n; x++) {for (int j = 0; j < i; j++)f[i][x] = (f[i][x] + (p * f[j][x + 1] + (1 - p + mod) * (x ? f[j][x - 1] : 0)) % mod *c[i - 1][j] % mod * g[i - j - 1][x] % mod) %mod;for (int j = 0; j <= i; j++)g[i][x] = (g[i][x] + c[i][j] * f[j][x] % mod * f[i - j][x] % mod) % mod;}}
}
signed main() {freopen("Cbracket.in", "r", stdin);freopen("Cbracket.out", "w", stdout);cin >> n >> X >> Y;p = X * qpow(Y, mod - 2) % mod;init_C();DP();int ans = f[n][0];for (int i = 1; i <= 2 * n; i += 2) ans = ans * qpow(i, mod - 2) % mod;cout << ans;
}
D
50pts
可以按如下方式进行DP,考虑 \(f_{i,j,k}\) 表示跨过 \(i\) 号点,梦梦执行了 \(j\) 次操作,熊熊执行了 \(k\) 次操作,且 \([1,i]\) 编号内的所有白魔法全都满足要求的情况下,当前的最小消耗是多少。
注意到 \(a_i \leq 10\),这个过程中 \(j \in [0,10],k \in [0,10]\),利用滚动数组,时间复杂度为 \(O(n \times M^2)\),其中 \(M\) 为值域。
100pts
考虑可能的白魔法序列 \(b\) 满足什么条件,可以考虑反向操作,将这个序列减为 \(0\)。 考虑差分,如果 \(b_i > b_{i+1}\),那么至少要进行 \(b_i − b_{i+1}\) 次前缀 \(i\) 减一的操作。反之亦然。 上述操作完之后,所有的元素都变成相同,如果这个时候 \(b\) 非负,那么满足条件。 为了方便,我们在序列开头和末尾添加两个足够大的元素 \(b_0 = b_{n+1} = M\),上述条件就是 \(b_0 ≥ ∑_n ^i=\max(0, b_i − b_{i+1})\)。
因为 \(b_0=b_{n+1}=M\) 足够大,所以上述条件可以转化为
在原问题中,代价总和就等于 \(b_i\) 之和,所以问题可以转化为,可以花 \(1\) 的代价让 \(a_i\) 加上 \(1\),问最少的代价满足上述条件。
令 \(F = \sum_{i=0}^n |a_i − a_{i+1}|\)。每次我们可以选择一段极长的相 同的 \(a_l = a_{l+1} = · · · = a_r\),并且 \(a_{l−1} > a_l , a_{r+1} > a_r\),将这一段加一就能将 \(F\) 整体地减少 \(2\)。这样操作的代价为这一段的长度。 我们贪心地找这样最短的极长段即可,可以对这一段加,然后加到这一段与两头的某个数字相同为止。一直做到 \(F\) 不超过 \(2M\)。 考虑段合并的过程,最后一定是所有数和最大的元素合并, 往前就是左右两边独立合并。这个与笛卡尔树比较类似。 我们求出笛卡尔树之后,就能快速得到所有段合并的过程, 并且从短到长贪心即可。时间复杂度 \(O(n)\)。
#include <bits/stdc++.h>
using namespace std;
#define x first
#define y second
#define mp(Tx, Ty) make_pair(Tx, Ty)
#define For(Ti, Ta, Tb) for (auto Ti = (Ta); Ti <= (Tb); Ti++)
#define Dec(Ti, Ta, Tb) for (auto Ti = (Ta); Ti >= (Tb); Ti--)
#define debug(...) fprintf(stderr, __VA_ARGS__)
#define range(Tx) begin(Tx), end(Tx)
const int N = 1e5 + 5;
long long a[N];
int n, t, fal[N], far[N];
void init(int n)
{for (int i = 0; i <= n + 1; i++)fal[i] = far[i] = i;
}
int find(int x, int fa[])
{if (x == fa[x])return x;fa[x] = find(fa[x], fa);return fa[x];
}
void U(int x, int y, int fa[]) { fa[find(y, fa)] = find(x, fa); }
int main(){assert(freopen("Dmagic.in", "r", stdin));assert(freopen("Dmagic.out", "w", stdout)); cin.tie(nullptr)->sync_with_stdio(false);scanf("%d", &t);while (t--){memset(a, 0, sizeof(a));scanf("%d", &n);For(i, 1, n) scanf("%lld", &a[i]);init(n);a[0] = 3e9, a[n + 1] = 3e9;long long tot = 6e9;long long now = 0;For(i, 0, n) now += abs(a[i + 1] - a[i]);priority_queue<pair<int, pair<int, int>>, vector<pair<int, pair<int, int>>>, greater<pair<int, pair<int, int>>>> q;For(i, 1, n) if (a[i] == a[i - 1]) U(i - 1, i, fal);Dec(i, n, 1) if (a[i] == a[i + 1]) U(i + 1, i, far);For(i, 1, n) if (find(i, fal) == i){int l = i, r = find(i, far);if (a[l - 1] > a[i] && a[r + 1] > a[i])q.push(mp(r - l + 1, mp(min(a[l - 1] - a[i], a[r + 1] - a[i]), i)));}while (now > tot && !q.empty()){auto t = q.top();q.pop();int l = t.y.y, r = t.y.y + t.x - 1;int minn = min(a[l - 1], a[r + 1]);if (now - (minn - a[l]) * 2 < tot){long long k = now - tot;k = (k + 1) / 2;a[l] = a[r] = a[l] + k;break;}now = now - (minn - a[l]) * 2;a[l] = a[r] = minn;if (a[l] == a[l - 1])U(l - 1, l, fal), U(l, l - 1, far);if (a[r] == a[r + 1])U(r + 1, r, far), U(r, r + 1, fal);l = find(l, fal), r = find(r, far);if (a[l - 1] > a[l] && a[r + 1] > a[r]){q.push(mp(r - l + 1, mp(min(a[l - 1] - a[l], a[r + 1] - a[r]), l)));}}For(i, 1, n){int father = find(i, fal);a[i] = a[father];}long long ans = 0;For(i, 1, n) ans += a[i];printf("%lld\n", ans);}return 0;
}