11.26 CW 模擬賽 賽時記錄

Yorg發表於2024-11-26

看題

也是給他綁上 \(\rm{Subtask}\) 了, 這我騙雞毛分啊

感冒也是非常難受, 但是事已至此, 先讀題吧


題目背景好看愛看

\(\rm{T1}\)

圖論題, 好玩

\(\rm{T2}\)

大概也是不會做, 再說

\(\rm{T3}\)

難繃, 考慮高檔暴力

\(\rm{T4}\)

這個肯定是暴力都難打


今天也是 \(30 \rm{min} + 1 \rm{h} + 15 \rm{min} + 1 \rm{h} 15 \rm{min}\)

\(\rm{T1}\)

先分析題意,

給定一張無重邊, 無環的無向圖
求加若干邊之後, 圖中最長鏈能達到多長

首先發現最後的圖一定包含了全部的 \(n\) 個點, 因為如果不包含點 \(i\) , 那麼選上點 \(i\) 之後, 最長的鏈一定會變得更長

容易的, 我們發現原圖應該是一個森林, 那麼我們需要把原圖中的森林連線起來, 使得最長鏈最長, 符合直覺的, 我們應該去連線每顆樹的直徑

那麼好, 這題結束了


首先讀入是顯然的, 對於每一個連通塊, 兩遍 \(\rm{dfs}\) 求直徑的兩端

對於第 \(i\) 個連通塊, 我們考慮將 \(i - 1\) 連通塊的右側 (這裡的左右人為指定) , 和 \(i\) 的左側連線起來, 對於 \(i = 1/n\) 情況特殊處理

\(\rm{T2}\)

只過了 \(25 \rm{min}\) , 沖沖衝

\(\rm{T1}\) 多半是大眾分, 當然我多半拿不到大眾分就是了 (確實沒拿到)


轉化題意

對於序列 \(s\) , 找出一段區間 \([L, R]\) , 使得區間長度至少為 \(k\) 的前提下, 令所有數的 \(\gcd\)\(g\) , 求 \(\displaystyle g \times \sum_{i = L}^{R} s_i \to \max\)

題意其實給的很清楚了, 不太需要轉化

區間長是 \(10^6\) 級別的, 不好處理

暴力的做法是, 考慮對於一個固定的 \(R\) , 我們向前走, 記錄 \(\sum\) 值和 \(\gcd\) , 可以做到 \(\mathcal{O} (n ^ 2)\) 的時間複雜度, \(30 \rm{pts}\)

我們還需要最佳化, 這裡最佳化的點很明確, 我們需要利用之前的計算結果, 不能再次重複計算

但是並不好利用之前的計算結果, 考慮神秘最佳化

注意到當右端點確定, \(\gcd\) 的趨勢是單調不增, 我們考慮預處理出所有 \(\gcd\) 的變化區間的最左側點, 只考慮最左端點即可, 期望上是 \(\mathcal{O} (n \sqrt{n})\) 的, 可以拿到 \(60 \rm{pts}\)


一會再回來想一想

對於右端點的每一次拓張, 我們向左列舉到第一個值為當前 \(s_i\) 因數的最左側點, 中間的全部都要並過來, 即在棧中彈出, 如果左邊沒有因數, 彈出完了之後插入一個 \(1\) , 特別的, 判斷一下 \(s_i\) 和當前棧頭的關係, 看 \(s_i\) 是否需要單開區間

這個可以拿棧處理

\(\rm{T3}\)

轉化題意

對於序列 \(s\) , 找出多少個區間 \([L, R]\) 滿足其中有一半以上的元素相同

對於 \(\rm{Subtask} 1, 2\) , \(\mathcal{O} (n ^ 2 \log n)\) 演算法可以解決

對於 \(\rm{Subtask} 3\) ,

我們先預處理出兩種數在區間中的出現次數的差出現次數的字首和, 然後就簡單了

程式碼

沒有好實現的, 按分值打

\(\rm{T1}\)

寫的差不多, 看看會不會掛

#include <bits/stdc++.h>
#define int long long
const int MAXN = 1e5 + 20;

#define FILE_IO

int n, m;

class Graph_Class
{
private:

public:
    /*並查集*/
    struct DSU_struct
    {
        int fa[MAXN];
        void fa_init() { for (int i = 1; i <= n; i++) fa[i] = i; }

        int find(int x) {
            return fa[x] = (x == fa[x]) ? x : find(fa[x]);
        }

        void merge(int u, int v) {
            int fa_u = find(u), fa_v = find(v);
            fa[fa_v] = fa_u;
        }
    } DSU;

    struct node
    {
        int to;
        int next;
    } Edge[MAXN << 1];
    int Edge_cnt = 0;
    int head[MAXN];
    void Edge_init() { for (int i = 1; i <= m * 2; i++) Edge[i].next = -1; }
    void head_init() { for (int i = 1; i <= n; i++) head[i] = -1; }
    void addedge(int u, int v) {
        Edge[++Edge_cnt].to = v;
        Edge[Edge_cnt].next = head[u];
        head[u] = Edge_cnt;
    }
} Graph;

class Sol_Class
{
private:
    int dist[MAXN];
    void dfs1(int Now, int fa, int d) {
        dist[Now] = d;
        for (int i = Graph.head[Now]; ~i; i = Graph.Edge[i].next) {
            int NowTo = Graph.Edge[i].to;
            if (NowTo == fa) continue;

            dfs1(NowTo, Now, d + 1);
        }
    }

    int CalcD(int NowTree) {
        for (int i = 1; i <= n; i++) {
            if (Graph.DSU.find(i) == NowTree) {
                dfs1(i, -1, 1);
                break;
            }
        }

        int Root = -1, maxdis = 0;
        for (int i = 1; i <= n; i++) {
            if (Graph.DSU.find(i) != NowTree) continue;
            if (dist[i] > maxdis) maxdis = dist[i], Root = i;
        }

        memset(dist, 0, sizeof(dist));
        dfs1(Root, -1, 1);

        Root = -1, maxdis = 0;
        for (int i = 1; i <= n; i++)
        {
            if (Graph.DSU.find(i) != NowTree) continue;
            if (dist[i] > maxdis) maxdis = dist[i], Root = i;
        }

        return maxdis;
    }

public:
    bool Treevis[MAXN];
    int Ans = 0;
    /*分割森林, 計算直徑*/
    void solve()
    {
        for (int i = 1; i <= n; i++) {
            int NowTree = Graph.DSU.find(i);
            if (Treevis[NowTree]) continue;

            Ans += CalcD(NowTree);
            Treevis[NowTree] = true;
        }

        printf("%lld", Ans);
    }
} Sol;

signed main()
{
#ifdef FILE_IO
    freopen("chariot.in", "r", stdin);
    freopen("chariot.out", "w", stdout);
#endif

    scanf("%lld %lld", &n, &m);
    Graph.DSU.fa_init();
    Graph.head_init();
    Graph.Edge_init();
    for (int i = 1; i <= m; i++) {
        int u, v;
        scanf("%lld %lld", &u, &v);
        Graph.addedge(u, v), Graph.addedge(v, u);
        Graph.DSU.merge(u, v);
    }

    Sol.solve();

    return 0;
}

\(\rm{T2}\)

稍微複雜, 看運氣了, 話說我今天 \(\rm{luogu}\) 都沒打卡

最佳化方法可能是錯的, 跑不過大樣例, 只能留在後面看看能騙多少

#include <bits/stdc++.h>
#define int long long
const int MAXN = 1e6 + 20;

#define FILE_IO

int n, k;
int h[MAXN];
int Sum[MAXN];

class Subtask_3
{
private:
    /*棧*/
    struct node {
        int Pos;
        int Val;
    } ;
    struct Stack_struct
    { 
        node Stack[MAXN];
        int tp = 0;

        bool empty() {
            return tp == 0;
        }

        void pop() {
            tp--;
        }

        void push(node x) {
            Stack[++tp] = x;
        }

        node top() {
            return Stack[tp];
        }
    } S;

public:

/*處理*/
    void solve()
    {
        int Ans = 0;
        for (int i = 1; i <= n; i++)
        {
            /*處理 gcd 左端點*/
            while (!S.empty()) {
                node Now = S.top();
                if (h[i] % Now.Val == 0) break;
                S.pop();
            }

            if (S.empty()) S.push({1, 1});
            if (h[i] != S.top().Val) S.push({i, h[i]});

            /*計算答案*/
            for (int j = 1; j <= S.tp; j++) {
                int NowAns = (Sum[i] - Sum[S.Stack[j].Pos - 1]) * S.Stack[j].Val;
                if (i - S.Stack[j].Pos + 1 < k) continue;
                Ans = std::max(Ans, NowAns);
            }
        }

        printf("%lld", Ans);
    }
} S_3;

class Subtask_12
{
private:

public:
    void solve()
    {
        int Ans = 0;
        for (int i = 1; i <= n; i++) {
            int Sum = 0, Gcd  = h[i];
            for (int j = i; j >= 1; j--) {
                Sum += h[j];
                Gcd = std::__gcd(Gcd, h[j]);

                if (i - j + 1 >= k) Ans = std::max(Ans, Sum * Gcd);
            }
        }

        printf("%lld", Ans);
    }
} S_12;

signed main()
{
#ifdef FILE_IO
    freopen("intelligence.in", "r", stdin);
    freopen("intelligence.out", "w", stdout);
#endif
    scanf("%lld %lld", &n, &k);
    for (int i = 1; i <= n; i++)
        scanf("%lld", &h[i]), Sum[i] = Sum[i - 1] + h[i];
    
    if (n > 10000) S_3.solve();
    else S_12.solve();

    return 0;
}

只能趕緊衝 \(\rm{T3}\) , 最佳化思路能騙 \(10 \rm{pts}\) 就非常好了

\(\rm{T3}\)

沒打完, 寄