24山东省赛vp补题

I Left Shifting

知识点:签到题,无
思路:先判断字符串一开始是否满足条件,若不满足从第一个开始遍历,若出现 \(s[i]==s[i+1]\),那么输出 \(i+1\) 即可,若遍历完也没发现,输出 \(-1\)

A Printer

知识点:二分

思路:对秒数进行二分,然后跑check检查,注意跑的时候如果发现 \(sum>=k\),一定要直接返回 \(1\),不然会爆数据范围。

F Divide the Sequence

知识点:贪心,前缀和

这个答案应该是求一个长式子的最大值,对其进行化简。

假设整个序列的总和为 $S=\displaystyle\sum_{j=1}^{n} a_{j \text { 。 }} $
设划分点为 $ r_{1}, r_{2}, \ldots, r_{k-1}$ ,前缀和为 $P_{x}=\displaystyle\sum_{j=1}^{x} a_{j} $ 。 那么:

\[\begin{array}{rl} s_{1}&=P_{r_{1}}\\ s_{2}&=P_{r_{2}}-P_{r_{1}} \\ s_{3}&=P_{r_{3}}-P_{r_{2}} \\ &\vdots \\ s_{k}&=P_{n}-P_{r_{k-1}} \end{array} \]

代入目标公式:

\[F(k)=1 \cdot P_{r_{1}}+2 \cdot\left(P_{r_{2}}-P_{r_{1}}\right)+3 \cdot\left(P_{r_{3}}-P_{r_{2}}\right)+\cdots+k \cdot\left(P_{n}-P_{r_{k-1}}\right) \]

展开并合并同类项:

\[\begin{array}{rl} F(k)&=P_{r_{1}}+2 P_{r_{2}}-2 P_{r_{1}}+3 P_{r_{3}}-3 P_{r_{2}}+\cdots+k P_{n}-k P_{r_{k-1}} \\ F(k)&=k \cdot P_{n}-\left(P_{r_{1}}+P_{r_{2}}+\cdots+P_{r_{k-1}}\right) \end{array} \]

被减数是固定的,那么只需要使得减数很小就行了,所以取最小的几个前缀和就可以了,对原数组进行前缀和,然后对前缀和进行排序,再进行前缀和,根据k的大小取值就可以了

Code

点击查看代码
void solve()
{
    cin >> n;
    vi a(n + 1);
    vi pre(n + 1);
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        pre[i] = pre[i - 1] + a[i];
    }
    sort(pre.begin() + 1, pre.begin() + n);
    vi b(n + 1);
    for (int i = 1; i < n; i++)
    {
        b[i] = b[i - 1] + pre[i];
    }
    for (int i = 1; i <= n; i++)
    {
        if (i == 1)
        {
            cout << pre[n] << ' ';
            continue;
        }
        cout << i * pre[n] - b[i - 1] << ' ';
    }
    cout << endl;
}

K Matrix

知识点:构造

把1,2,3,4放到四角,四个边上全是5,然后对内部进行构造。

不难发现,就算穿插着放一个相同数字,总会有情况不满足,所以 \(n\) 行只能放一行,列也是一样,这样一共可以放 \(5+n-2+n-2-1=n\) 个数字,恰好放完。

J Colorful Spanning Tree

知识点:最小生成树kruskal

思路:把边都存起来,按权值从小到大排序,遍历所有边,
如果两者根相同且根不连通,说明这两个点是同一颜色的,需要连 \(a[i]-1\) 个边,如果两者根相同且根连通,说明这两个点连过了,不需要再连
如果两者根不同且两者均不连通,说明两个点分属于两个块,需要从左块连 \(a[v]\) 条边,从右块往左块连 \(a[u]-1\) 条边
如果两者根不同且有一个连通,只需要将未连通的连到连通块上即可

Code

点击查看代码
int fa[M];
void init()
{
    for (int i = 1; i <= n; i++)
    {
        fa[i] = i;
    }
}
int find(int a)
{
    return fa[a] == a ? a : fa[a] = find(fa[a]);
}
bool same(int a, int b)
{
    return find(a) == find(b);
}
void join(int a, int b)
{
    a = find(a);
    b = find(b);
    if (a != b)
    {
        fa[a] = b;
    }
}
struct node
{
    int u, v, w;
};
bool cmp(node a, node b)
{
    return a.w < b.w;
}

void solve()
{
    cin >> n;
    vector<node> adj;
    init();
    vi a(n + 1);
    vi vis(n + 1);
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    vvi b(n + 1, vi(n + 1));
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            cin >> b[i][j];
        }
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = i; j <= n; j++)
        {
            adj.push_back({i, j, b[i][j]});
        }
    }
    sort(all(adj), cmp);
    int ans = 0;
    for (auto [u, v, w] : adj)
    {
        u = find(u);
        v = find(v);
        cerr << u << ' ' << v << ' ' << w << endl;
        if (u == v)
        {
            if (!vis[u])
            {
                vis[u] = 1;
                ans += (a[u] - 1) * w;
            }
            continue;
        }
        if (vis[u] && vis[v])
        {
            ans += w;
            fa[u] = v;
            continue;
        }
        if (vis[u])
        {
            ans += (a[v] * w);
            fa[v] = u;
            continue;
        }
        if (vis[v])
        {
            ans += (a[u] * w);
            fa[u] = v;
            continue;
        }
        ans += (a[v] + a[u] - 1) * w;
        fa[u] = v;
        vis[v] = 1;
    }
    cout << ans << endl;
}

D Hero of the Kingdom

知识点:数学

思路:从a地运到b地,肯定是买卖数相同才赚的最多,最省时间,所以 \(x=y\)
同时要注意时间不会超时,如果发现还有时间,就可以再拿着赚到的钱再回去买,就相当于重复前面这个过程。
**注意:如果每次收益不够买到下一个物品,而且时间花费小,时间大的话,一定会超时,所以要进行优化,找出“一次性买x个商品这样的循环”还会持续多少次,记为k,应该有时间和金钱两个限制,金钱上,应该有 \(k*(q-p)*x>=p*(x+1)-m\) **,意思为“以我当前每次赚的钱还需要赚多少次才能满足买 \(x+1\) 个”;时间上,应该有 \(t>=k*((a + c) * x + b + d)\)

Code

点击查看代码
void _()
{
    int q, p, a, b, c, d, m, t;
    cin >> p >> a >> b >> q >> c >> d >> m >> t;

    while (1)
    {
        if (t <= b + d || m < p)
            break;
        int x = (t - b - d) / (a + c);
        if (x == 0)
            break;
        if (p * x > m)
        {
            x = m / p;
        }

        int tmp = min((p * (x + 1) - m + (q - p) * x - 1) / ((q - p) * x), t / ((a + c) * x + b + d) - 1);
        if (tmp <= 0)
        {
            m += (q - p) * x;
            t -= (a + c) * x + b + d;
        }
        else
        {
            m += (q - p) * x * tmp;
            t -= ((a + c) * x + b + d) * tmp;
        }
    }
    cout << m << endl;
}

C Colorful Segments 2

知识点:单调队列,(权值线段树)

思路:对于所有线段,按左端点的大小进行排序,那么对每一条边来说,只需要看当前多少条边的右端点比自己的左端点大就可以了,用优先队列小根堆维护一个单调队列,使得队列中所有元素都比当前线段的左端点大就可以了。

点击查看代码
struct node
{
    int l, r;
};
bool cmp(node a, node b)
{
    return a.l < b.l;
}
void solve()
{
    cin >> n >> k;
    vector<node> a(n);
    for (int i = 0; i < n; i++)
    {
        cin >> a[i].l >> a[i].r;
    }
    sort(all(a), cmp);
    priority_queue<int, vector<int>, greater<int>> p;
    int ans = 1;
    for (int i = 0; i < n; i++)
    {
        while (!p.empty() && p.top() < a[i].l)
        {
            p.pop();
        }
        int now = k - p.size();
        if (now <= 0)
        {
            ans = 0;
            break;
        }
        ans = ans * now % mod;
        p.push(a[i].r);
    }
    cout << ans << endl;
}
posted @ 2026-04-10 14:17  Lambda_L  阅读(4)  评论(0)    收藏  举报