【题解】NOIP 13连测 #1

NOIP13连测 #1

A

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

贪心!

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

  • +40pts

​ 假设两个点的横坐标和纵坐标之差分别为 x,y,问题等价于最大化 x+yx2+y2 容易发现在 x=y 时取到最大值 2

​ 当 xi+yi=xj+yj 时,xixj=yjyi,两个点的横纵坐标之差恰好相同,所以答案为 2

​ 当 xiyi=xjyj 时,xixj=yiyj,两个点的横纵坐标之差恰好相同,所以答案为 2

  • 100pts

​ 给出结论,将所有点按 xi+yi 排好序或按 xiyi 排好序,把任意两个相邻节点的答案取最大值,即为最终答案。

​ 以下给出具体证明。

​ 根据部分分的启发,容易发现两点连线和 x 轴的坐标越靠近 45°135° 答案越优。

​ 我们把整个坐标轴旋转一下,原本点 (x,y) 变成 x+y2,xy2,即以 y=xy=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+(n1)k 这些数两两不同。

那么按照这个顺序,第一次操作取 (1,1+k),第二次操作取 (1+3k,1+4k) 以此类推,即可连出一条链。

100pts
我们把所有数按如下方式写出来。

0k2k3k4k11+k1+2k1+3k1+4k22+k2+2k2+3k2+4k33+k3+2k3+3k3+4k44+k4+2k4+3k4+4k 

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

n=25,k=5 为例

0510152016111621271217223813182349141924

因为 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×22n),实际复杂度远小于这个值,因为并不是所有括号匹配都能够最终得到。

70pts

容易发现,括号序列一共有 135...(2𝑛1) 种生成方式。

假如 (1)1,那么一个序列合法的充要条件为:最小前缀和为 0,且以 0 结尾。

现在考虑维护这些前缀和。

如果我们在当前序列某一位后插入一个 (),且那一位的前缀和为 𝑥,那么相当于把 𝑥 替换成 [𝑥,𝑥+1,𝑥]

同理可知,插入 )( 相当于把 𝑥 替换成 [𝑥,𝑥1,𝑥]​。

我们观察到每次选中的数概率是均等的,所以我们并不关心数的顺序,只关心这个前缀和数组中每个数出现了多少次。

定义 𝑓𝑛,𝑥 为前缀和数组一开始只有一个数,这个数为 x,执行 𝑛 次操作以后得到的前缀和数组中每个元素均不小于 0 的概率。

有两种转移,第一种下一步变成了 x,x+1,x,那么概率为

1i+j+kn1fi,x×fj,x1×fk,x+1×Ci+j+ki×Cj+kj×p

第一种下一步变成了 x,x1,x​,那么概率为

1i+j+kn1fi,x×fj,x1×fk,x+1×Ci+j+ki×Cj+kj×(1p)

最终答案即为 f0,n,时间复杂度为 O(n4)

100pts

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

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

gn,x=1i+jnfi,x×fj,x+1×Cni

g 的计算是 O(n3) 的,再通过 g 计算第一种情况:

1i+jngi,x×fj,x×Cni

相当于对于 i,j,k 三维不一起枚举,先合并 i,j,再合并 j,k,时间复杂度为 O(n3)

#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,考虑 fi,j,k 表示跨过 i 号点,梦梦执行了 j 次操作,熊熊执行了 k 次操作,且 [1,i] 编号内的所有白魔法全都满足要求的情况下,当前的最小消耗是多少。

注意到 ai10,这个过程中 j[0,10],k[0,10],利用滚动数组,时间复杂度为 O(n×M2),其中 M 为值域。

100pts

考虑可能的白魔法序列 b 满足什么条件,可以考虑反向操作,将这个序列减为 0。 考虑差分,如果 bi>bi+1,那么至少要进行 bibi+1 次前缀 i 减一的操作。反之亦然。 上述操作完之后,所有的元素都变成相同,如果这个时候 b 非负,那么满足条件。 为了方便,我们在序列开头和末尾添加两个足够大的元素 b0=bn+1=M,上述条件就是 b0ni=max(0,bibi+1)

因为 b0=bn+1=M 足够大,所以上述条件可以转化为

i=0n|bibi+1|2M

在原问题中,代价总和就等于 bi 之和,所以问题可以转化为,可以花 1 的代价让 ai 加上 1,问最少的代价满足上述条件。

F=i=0n|aiai+1|。每次我们可以选择一段极长的相 同的 al=al+1=···=ar,并且 al1>al,ar+1>ar,将这一段加一就能将 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 @   Star_F  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示