【题解】NOIP 13连测 #1

NOIP13连测 #1

A

首先根据数据范围判断时间复杂度为 \(O(n\log n)\),考虑排序。
常见 trik:把坐标轴旋转 45°,原本坐标 \((x,y)\) 变成 \({\frac{x+y}{2},\frac{x-y}{2}}\)
最大值为最接近 \(y=x\)\(y=-x\) ,从小排序后相邻的两个点最小值即为答案。

贪心!

  • 40pts
    一一枚举任意两个点,进行最大值的求解。

  • +40pts

​ 假设两个点的横坐标和纵坐标之差分别为 \(x,y\),问题等价于最大化 \(\frac{x+y}{\sqrt{x^2+y^2}}\) 容易发现在 \(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
我们把所有数按如下方式写出来。

\[\begin{matrix} 0 & k & 2k & 3k & 4k \\ 1 & 1+k & 1+2k & 1+3k & 1+4k \\ 2 & 2+k & 2+2k & 2+3k & 2+4k \\ 3 & 3+k & 3+2k & 3+3k & 3+4k \\ 4 & 4+k & 4+2k & 4+3k & 4+4k \ \end{matrix} \]

其中矩阵的列为 \(\gcd(n,k)\)

\(n=25,k=5\) 为例

\[\begin{matrix} 0 & 5 & 10 & 15 & 20 \\ 1 & 6 & 11 & 16 & 21 \\ 2 & 7 & 12 & 17 & 22 \\ 3 & 8 & 13 & 18 & 23 \\ 4 & 9 & 14 & 19 & 24 \end{matrix} \]

因为 \(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\),那么概率为

\[\sum_{1 \leq i+j+k \leq n-1} f_{i,x} \times f_{j,x-1} \times f_{k,x+1} \times C_{i+j+k}^i \times C_{j+k}^j \times p \]

第一种下一步变成了 \(x,x-1,x\)​,那么概率为

\[\sum_{1 \leq i+j+k \leq n-1} f_{i,x} \times f_{j,x-1} \times f_{k,x+1} \times C_{i+j+k}^i \times C_{j+k}^j \times (1-p) \]

最终答案即为 \(f_{0,n}\),时间复杂度为 \(O(n^4)\)

100pts

只需要将上面的 DP 过程优化即可,优化的思路很多,以下给出一种。

以第一种转移为例,引入辅助DP数组 \(g\)

\[g_{n,x}=\sum_{1 \leq i+j \leq n} f_{i,x} \times f_{j,x+1} \times C_{n}^i \]

\(g\) 的计算是 \(O(n^3)\) 的,再通过 \(g\) 计算第一种情况:

\[\sum_{1 \leq i+j \leq n} g_{i,x} \times f_{j,x} \times C_{n}^i \]

相当于对于 \(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\) 足够大,所以上述条件可以转化为

\[\sum_{i=0}^n |b_i-b_{i+1}| \leq 2M \]

在原问题中,代价总和就等于 \(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;
}
posted @ 2025-02-06 14:48  Star_F  阅读(29)  评论(0)    收藏  举报