20260715

今天是自由练习

P1948 [USACO08JAN] Telephone Lines S

思路

考虑二分答案,也就是路径中第 \(k - 1\) 长的电线。

每次判断答案时,建图。设当前判断的答案为 \(x\),则将边权大于 \(x\) 的边权值设为 \(1\),小于等于 \(x\) 的可以忽略,权值设为 \(0\) 即可。

接着对刚刚建出来的图跑一边 dijstra,返回 \(1\)\(n\) 的最短距离即可。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2e4 + 5;
int n, p, k, head[N], cnt, maxx, ans = -1, vis[N], dis[N];
struct edge {
    int v, w, nxt;
} e[N * 2];
struct input_edge {
    int a, b, l;
} a[N];

void addEdge(int x, int y, int z) {
    e[++cnt].v = y;
    e[cnt].w = z;
    e[cnt].nxt = head[x];
    head[x] = cnt;
}
int check(int x) {
    cnt = 0;
    for (int i = 1; i <= n; i++) {
        head[i] = 0;
        dis[i] = 1e9;
        vis[i] = 0;
    }
    
    for (int i = 1; i <= p; i++) {
        int weight = (a[i].l > x) ? 1 : 0;
        addEdge(a[i].a, a[i].b, weight);
        addEdge(a[i].b, a[i].a, weight);
    }

    deque<int> q;
    q.push_back(1);
    dis[1] = 0;
    vis[1] = 1;

    while (!q.empty()) {
        int u = q.front();
        q.pop_front();
        vis[u] = 0;

        for (int i = head[u]; i; i = e[i].nxt) {
            int v = e[i].v;
            int w = e[i].w;
            if (dis[v] > dis[u] + w) {
                dis[v] = dis[u] + w;
                if (!vis[v]) {
                    vis[v] = 1;
                    if (w == 0) q.push_front(v);
                    else q.push_back(v);
                }
            }
        }
    }

    return dis[n];
}

signed main() {
    cin >> n >> p >> k;
    for (int i = 1; i <= p; i++) {
        cin >> a[i].a >> a[i].b >> a[i].l;
        maxx = max(maxx, a[i].l);
    }
    if (check(maxx + 1) == 1e9) {
        cout << -1 << endl;
        return 0;
    }

    int l = 0, r = maxx;
    ans = -1;

    while (l <= r) {
        int mid = (l + r) / 2;
        int res = check(mid);

        if (res <= k) {
            ans = mid;
            r = mid - 1;
        } else {
            l = mid + 1;
        }
    }

    cout << ans << endl;
    return 0;
}

P2343 宝石管理系统

这题数据好水。

思路

使用一个vector 来维护宝石。初始将所有宝石有序的加入vector。

插入操作就二分一下插入的位置,然后用 vector 自带的 insert 函数插入即可(但是这样插入好似是 \(O(n)\) 的,但是数据太水了还是过了。

查询操作直接输出 vector 中对应的数即可。

正解应该是平衡树之类的东西,但是我没写。

题解区还有不用二分纯暴力卡过的。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int m, q;
signed main()
{
    cin >> m >> q;
    vector<int> v;
    for(int i = 1; i <= m; i ++)
    {
        int x; cin >> x;
        v.insert(upper_bound(v.begin(), v.end(), x), x);
    }
    while(q --)
    {
        int c, n;
        cin >> c >> n;
        if(c == 1)
        {
            cout << v[v.size() - n] << endl;
        }
        else
        {
            v.insert(upper_bound(v.begin(), v.end(), n), n);
        }
    }
}

P1063 [NOIP 2006 提高组] 能量项链

水,但是我怎么没做出来。

暴力dp即可。

思路

由于项链是个环形的,考虑将环形拆成一个复制两遍的线形(长度为 \(2 \times n\)

\(dp[i][j]\)\(a[i]\)\(a[j]\) 中全部合并释放的最大能量。

我们再枚举一个 \(k\) 表示其中的断点,也就是 \(i\)\(k\) 的与 \(k + 1\)\(j\) 的合并。

则有状态转移方程 \(dp[j][i] = max(dp[j][i], dp[k + 1][i] + dp[j][k] + a[j] * a[k + 1] * a[i + 1])\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n, a[205], ans, dp[205][205], dp2[205][205];
signed main()
{
    cin >> n;
    for(int i = 1; i <= n; i ++)
    {
        cin >> a[i];
        a[i + n] = a[i];
    }
    for(int i = 0; i <= 2 * n; i ++) for(int j = 0; j <= 2 * n; j ++) dp[i][j] = 2e18;
    for(int i = 2; i < 2 * n; i ++)
    {
        for(int j = i - 1; j > 0 && i - j < n; j --)
        {
            for(int k = j; k < i; k ++)
            {
                // cout << j << " " << i << endl;
                dp[j][i] = max(dp[j][i], dp[k + 1][i] + dp[j][k] + a[j] * a[k + 1] * a[i + 1]);
            }
            ans = max(ans, dp[j][i]);
        }
    }
    cout << ans << endl;
}

P1880 [NOI1995] 石子合并

和上一题差不多。

思路

和上一题一样。

维护区间石子数用前缀和就行。

状态转移方程和上一题差不多。

注意,只有区间长度恰好等于 \(n\) 时才能更新答案。求最小值的dp需要初始化为 \(inf\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n, a[205], ans1, dp[205][205], dp2[205][205], sum[205], ans2 = 2e18;
signed main()
{
    cin >> n;
    for(int i = 1; i <= n; i ++)
    {
        cin >> a[i];
        a[i + n] = a[i];
    }
    for(int i = 1; i <= 2 * n; i ++)
    {
        sum[i] = sum[i - 1] + a[i];
    }
    for(int i = 0; i <= 2 * n; i ++)
    {
        for(int j = 0; j <= 2 * n; j ++) dp2[i][j] = 2e18;
        dp2[i][i] = 0;
    }
    for(int i = 2; i < 2 * n; i ++)
    {
        for(int j = i - 1; j > 0 && i - j < n; j --)
        {
            for(int k = j; k < i; k ++)
            {
                // cout << j << " " << i << endl;
                dp[j][i] = max(dp[j][i], dp[k + 1][i] + dp[j][k] + sum[i] - sum[j - 1]);
                dp2[j][i] = min(dp2[j][i], dp2[k + 1][i] + dp2[j][k] + sum[i] - sum[j - 1]);
            }
            ans1 = max(ans1, dp[j][i]);
            if(i - j + 1 == n) ans2 = min(ans2, dp2[j][i]);
        }
    }
    cout << ans2 << endl << ans1 << endl;
}

P3146 [USACO16OPEN] 248 G

依旧区间dp。

思路

\(dp[i][j]\)\(i\)\(j\) 的区间中全部合并后的最大值。

则枚举断点 \(k\),如果 \(dp[i][k] == dp[k + 1][j]\)(有两个相同的数),则转移 \(dp[j][i] = max(dp[j][i], dp[j][k] + 1)\)

\(ans\) 数组记录 \(dp\) 中的最大值和初始给出的序列中的最大值(不然无法合并时便会得不到答案)。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n, a[250], dp[250][250], ans;
signed main()
{
    cin >> n;
    for(int i = 1; i <= n; i ++)
    {
        cin >> a[i];
        dp[i][i] = a[i];
        ans = max(ans, a[i]);
    }
    for(int i = 2; i <= n; i ++)
    {
        for(int j = i - 1; j > 0; j --)
        {
            for(int k = j; k < i; k ++)
            {
                if(dp[j][k] == dp[k + 1][i] && dp[j][k])
                {
                    dp[j][i] = max(dp[j][i], dp[j][k] + 1);
                    // cout << dp[j][i] << " " << j << " " << i << endl;
                    ans = max(ans, dp[j][i]);
                }
            }
        }
    }
    cout << ans << endl;
}
posted @ 2026-07-15 09:30  Luowj  阅读(7)  评论(0)    收藏  举报