2026暑期集训

0713

总结

  1. 多多调试,因为你想到的方法很可能是正解。
  2. 锻炼调题能力,不然在一道题上耗时太久。
  3. 多看题,先打好一定能拿到的部分分再去写其他题的正解。
  4. 不要认为你的分数已经够了就开始摆烂,因为你会做的别人也会。

T1 排列鞋子

T2 P3459 [POI 2007] MEG-Megalopolis

场切了,没有思维,全是码量。

考场上坚持调试这一题,虽然最后过了但是调试这一题的时间太久了,导致放弃了T4。

思路

首先注意到题目中给出的是一棵树。初始每一条边权为 \(1\) ,那么修改边相当于将边权置为 \(0\)

考虑把这棵树拍到 dfn 序上,并记录每个节点的深度。

由于每一棵子树在 dfn 序列中都是连续的。所以修改和查询操作相当于区间修改和单点查询。

考虑用线段树维护深度。

代码

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 250005;
int cnt, n, m, dep[N], dfn[N], dfn2[N];
struct node
{
    int l, r;
} a[N];
struct tree
{
    int data, lzy;
} w[N << 2];
vector<int> g[N];
bool InRange(int l, int r, int L, int R) { return (l >= L) && (r <= R); }
bool OutofRange(int l, int r, int L, int R) { return (r < L) || (l > R); }
void pushup(int u)
{
    w[u].data = w[u * 2].data + w[u * 2 + 1].data;
}
void pushdown(int u, int l, int r)
{
    if (w[u].lzy == 0)
        return;
    w[u * 2].lzy += w[u].lzy, w[u * 2 + 1].lzy += w[u].lzy;
    int mid = l + r >> 1;
    w[u * 2].data -= (mid - l + 1) * w[u].lzy;
    w[u * 2 + 1].data -= (r - mid) * w[u].lzy;
    w[u].lzy = 0;
}
void build(int u, int l, int r)
{
    if (l == r)
    {
        w[u].data = dep[l];
        return;
    }
    int mid = l + r >> 1;
    build(u * 2, l, mid);
    build(u * 2 + 1, mid + 1, r);
    pushup(u);
}
void update(int u, int l, int r, int L, int R)
{
    if (InRange(l, r, L, R))
    {
        w[u].lzy++;
        w[u].data -= r - l + 1;
        return;
    }
    if (OutofRange(l, r, L, R))
        return;
    int mid = l + r >> 1;
    pushdown(u, l, r);
    update(u * 2, l, mid, L, R);
    update(u * 2 + 1, mid + 1, r, L, R);
    pushup(u);
}
int query(int u, int l, int r, int L, int R)
{
    if (InRange(l, r, L, R))
        return w[u].data;
    if (OutofRange(l, r, L, R))
        return 0;
    int mid = l + r >> 1;
    pushdown(u, l, r);
    return query(u * 2, l, mid, L, R) + query(u * 2 + 1, mid + 1, r, L, R);
}
int dfs(int x, int fa, int sum)
{
    a[x] = {++cnt, cnt};
    dfn[x] = cnt;
    dfn2[cnt] = x;
    dep[cnt] = sum;
    for (int v : g[x])
    {
        if (v == fa)
            continue;
        a[x].r = dfs(v, x, sum + 1);
    }
    return a[x].r;
}
signed main()
{
    // freopen("meg.in", "r", stdin);
    // freopen("meg.out", "w", stdout);
    cin >> n;
    for (int i = 1; i < n; i++)
    {
        int a, b;
        cin >> a >> b;
        g[a].push_back(b);
        g[b].push_back(a);
        // cin >> dep[i];
    }
    dfs(1, 0, 0);
    build(1, 1, n);
    // for (int i = 1; i <= n; i++)
    // {
    //     cout << a[i].l << " " << a[i].r << endl;
    // }
    // for (int i = 1; i <= n; i++)
    // {
    //     cout << dfn[i] << " ";
    // }
    // cout << endl;
    // for (int i = 1; i <= n; i++)
    // {
    //     cout << query(1, 1, n, dfn[a[i].l], dfn[a[i].l]) << " ";
    // }
    cin >> m;
    for (int i = 1; i <= n + m - 1; i++)
    {
        char op;
        cin >> op;
        if (op == 'W')
        {
            int x;
            cin >> x;
            cout << query(1, 1, n, dfn[x], dfn[x]) << endl;
            // cout << dep[dfn[x]] << endl;
        }
        if (op == 'A')
        {
            int x, y;
            cin >> x >> y;
            update(1, 1, n, a[y].l, a[y].r);
            // for (int i = 1; i <= n; i++)
            // {
            //     cout << query(1, 1, n, a[i].l, a[i].l) << " ";
            // }
            // cout << endl;
        }
    }
}

T3 P3558 [POI 2013] BAJ-Bytecomputer

这道题之前考过,但是由于我之前的懒惰,一直都没有做这道题。

很好的一道分类讨论题。

思路

动态规划

猜测结论:按照优策略操作完后序列中只有 \(0\)\(1\)\(-1\)

\(dp[i][j]\) 为第 \(i\) 位变为 \(j - 1\) (防止数组下标为负),且序列合法时最小的操作次数。

初始化:\(dp[1][a[1] + 1]\)\(0\),其他全为 \(inf\)

然后就从 \(dp[2]\) 开始转移,分讨:

  • \(a[i] = -1\)

因为 \(a[i]\) 已经是 \(-1\) 了,所以 \(dp[i][0] = dp[i - 1][0]\)

\(a[i - 1]\) 初始为 \(1\) 时,\(a[i]\) 才有可能变为 \(0\),所以 \(dp[i][1] = min(dp[i - 1][0], dp[i - 1][1]) + 1\)。否则为 \(inf\)

同理,\(dp[i][2] = min(dp[i - 1][0], min(dp[i - 1][1], dp[i - 1][2])) + 2\)。但由于前一个数可以变成一了,所以当 \(a[i - 1] != 1\) 时,\(dp[i][2] = dp[i - 1][2] + 2\)

  • \(a[i] = 0\)\(a[i] = 1\) 时同理。

代码

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 5;
const int inf = 1e9;
int dp[N][3], a[N], n, ans = inf;
signed main()
{
	scanf("%lld", &n);
	for(int i = 1; i <= n; i ++) scanf("%lld", &a[i]);
	if(a[1] == -1) dp[1][1] = inf, dp[1][2] = inf;
	if(a[1] == 0) dp[1][0] = inf, dp[1][2] = inf;
	if(a[1] == 1) dp[1][0] = inf, dp[1][1] = inf;
	for(int i = 2; i <= n; i ++)
	{
		if(a[i] == -1)
		{
			dp[i][0] = dp[i - 1][0];
			if(a[i - 1] == 1) dp[i][1] = min(dp[i - 1][0], dp[i - 1][1]) + 1;
			else dp[i][1] = inf;
			if(a[i - 1] == 1) dp[i][2] = min(dp[i - 1][0], min(dp[i - 1][1], dp[i - 1][2])) + 2;
			else dp[i][2] = dp[i - 1][2] + 2;
		}
		if(a[i] == 0)
		{
			dp[i][0] = dp[i - 1][0] + 1;
			dp[i][1] = min(dp[i - 1][0], dp[i - 1][1]);
			if (a[i - 1] == 1) dp[i][2] = min(dp[i - 1][0], min(dp[i - 1][1], dp[i - 1][2])) + 1;
			else dp[i][2] = dp[i - 1][2] + 1; 
		}
		if(a[i] == 1)
		{
			dp[i][0] = dp[i - 1][0] + 2;
			if(a[i - 1] == -1) dp[i][1] = min(dp[i - 1][0], dp[i - 1][1]) + 1;
			else dp[i][1] = dp[i - 1][0] + 1;
			dp[i][2] = min(dp[i - 1][0], min(dp[i - 1][1], dp[i - 1][2]));
		}
	}
	for(int i = 0; i <= 2; i ++)
	{
		ans = min(ans, dp[n][i]);
	}
	// printf("%lld %lld %lld\n", dp[n][0], dp[n][1], dp[n][2]);
	if(ans >= inf) cout << "BRAK" << endl;
	else cout << ans << endl;
}

贪心

还有个贪心解法的,但是我还没有学懂。

别人的题解

T4 P9525 [JOIST 2022] 团队竞技 / Team Contest

这一道题我做过的,考场上也想出了正解的东西。但是写完代码发现答案不对之后就没有调试了(当时代码实现方式不对导致特别复杂难调)

思路

这题不是一眼反悔贪心吗。

考虑各选出三只某个能力值最大的海狸(尽管会有重复)。然后记录每一只海狸的编号,如果编号有重复,那么就换成一只能力值次大的。

一直重复上述步骤,直到选出的三只海狸合法。

如果直到最后都没有选出来,则没有合法方案。

发现可以用三个优先队列维护海狸的每种能力值。

代码

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
int n, a[N], b[N], c[N], vis[N];
priority_queue<pair<int, int> > A, B, C;
signed main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i] >> b[i] >> c[i];
        A.push(make_pair(a[i], i));
        B.push(make_pair(b[i], i));
        C.push(make_pair(c[i], i));
    }
    for (int i = 1; i <= n; i++)
    {
        int x = A.top().second, y = B.top().second, z = C.top().second;
        if (b[x] == b[y] || c[x] == c[z])
        {
            vis[x] = 1;
        }
            
        if (c[y] == c[z] || a[y] == a[x])
        {
            vis[y] = 1;
        }
            
        if (a[z] == a[x] || b[z] == b[y])
        {
            vis[z] = 1;
        }
            
        if (!vis[x] && !vis[y] && !vis[z])
        {
            cout << a[x] + b[y] + c[z];
            return 0;
        }
        while (!A.empty() && vis[A.top().second])
        {
            A.pop();
        }
            
        while (!B.empty() && vis[B.top().second])
        {
            B.pop();
        }
            
        while (!C.empty() && vis[C.top().second])
        {
            C.pop();
        }
            
    }
    cout << -1;
}

0714

T1 P3518 [POI 2011] SEJ-Strongbox

T2 P4095 [HEOI2013] Eden 的新背包问题

这一道题这么简单我怎么会做不出来。

考场思路

将所有情况全部预处理一遍,也就是二进制拆分后跑01背包。

然后询问时直接输出之前处理过的答案。

极限数据下大约要跑 \(3 \times 10 ^ {10}\)升级评测机后即可过

80pts

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n, dp[1005][1005], cnt, q;
int x, y, z, e, d, b;
struct node
{
    int v, w, id;
}a[10005];
signed main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i ++)
    {
        scanf("%d %d %d", &x, &y, &z);
        b = 1;
        for(; z - b >= 0; z -= b, b *= 2)
        {
            a[++ cnt] = {x * b, y * b, i};
        }
        if(z > 0) a[++ cnt] = {x * z, y * z, i};
    }
    for(int id = 1; id <= n; id ++)
    {
        for(int i = 1; i <= cnt; i ++)
        {
            if(a[i].id == id) continue;
            for(int j = 1000; j >= a[i].v; j --)
            {
                dp[id][j] = (dp[id][j] > dp[id][j - a[i].v] + a[i].w) ? dp[id][j] : dp[id][j - a[i].v] + a[i].w;
            }
        }
    }
    scanf("%d", &q);
    for(int i = 1; i <= q; i ++)
    {
        scanf("%d %d", &d, &e);
        printf("%d\n", dp[d + 1][e]);
    }
}

正解

分别从前往后和从后往前跑一边背包,存下来。

然后答案就是前面部分的 + 后面部分的了(刚好把询问的这个节点排除在外)。

但是东西都被二进制拆分掉了,但是拆除来的东西是连续的。

所以我们开一个 \(l\) 数组记录一个东西的左端点,再开一个 \(r\) 数组记录右端点。

发现前后分配的价值不是固定的,又发现价值特别的小,于是枚举价值计算最大值,得到答案

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n, dp[1005][1005], cnt, q;
int x, y, z, e, d, b;
struct node
{
    int v, w, id;
}a[10005];
signed main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i ++)
    {
        scanf("%d %d %d", &x, &y, &z);
        b = 1;
        for(; z - b >= 0; z -= b, b *= 2)
        {
            a[++ cnt] = {x * b, y * b, i};
        }
        if(z > 0) a[++ cnt] = {x * z, y * z, i};
    }
    for(int id = 1; id <= n; id ++)
    {
        for(int i = 1; i <= cnt; i ++)
        {
            if(a[i].id == id) continue;
            for(int j = 1000; j >= a[i].v; j --)
            {
                dp[id][j] = (dp[id][j] > dp[id][j - a[i].v] + a[i].w) ? dp[id][j] : dp[id][j - a[i].v] + a[i].w;
            }
        }
    }
    scanf("%d", &q);
    for(int i = 1; i <= q; i ++)
    {
        scanf("%d %d", &d, &e);
        printf("%d\n", dp[d + 1][e]);
    }
}

T3 P4343 [SHOI2015] 自动刷题机

考场上其实写出了正解代码的,但是check函数的返回类型搞错了(一直没发现)

二分太久没写都不会写了

思路

一看到这个最小值最大值就想到二分。

观察答案,用大脑直觉思考一下,发现答案似乎存在单调性。

所以就可以用二分解决问题了。

记录一个 \(ans\) 数组来判断无解

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 5;
int n, m, a[N], dp[N][2], maxx;
int check(int x)
{
    int now = 0, res = 0;
    for(int i = 1; i <= n; i ++)
    {
        now += a[i];
        if(now >= x)
        {
            res ++, now = 0;   // 提交后代码清空,不是减去 x
        }
        if(now < 0) now = 0;   // 删除超过已有代码时归零
    }
    return res;
}
signed main()
{
    scanf("%lld %lld", &n, &m);
    for(int i = 1; i <= n; i ++)
    {
        scanf("%lld", a + i);
    }
    int l = 1, r = 2e18;
    while(l <= r)
    {
        int mid = l + r >> 1;
        if(check(mid) <= m) r = mid - 1;
        else l = mid + 1;
    }
    int ans_min = r + 1;
    l = 1, r = 2e18;
    while(l <= r)
    {
        int mid = l + r >> 1;
        if(check(mid) >= m) l = mid + 1;
        else r = mid - 1;
    }
    int ans_max = l - 1;
    if(ans_min > ans_max || check(ans_min) != m) printf("-1");
    else printf("%lld %lld", ans_min, ans_max);
    return 0;
}

T4 P5771 [JSOI2016] 反质数序列

0715

今天是自由练习

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;
}

0719

P2071 座位安排

思路

二分图模板。

考虑将一排拆成两个座位,座位在右,人在左。

然后人去匹配座位即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n, vis[10005], ans, rec[10005];
vector<int> g[4005];
int dfs(int u, int t)
{
	if(vis[u] == t) return 0;
	vis[u] = t;
	for(int v : g[u])
	{
		if(rec[v] == 0 || dfs(rec[v], t))
		{
			rec[v] = u;
			return 1;
		}
	}
	return 0;
}
signed main()
{
	cin >> n;
	for(int i = 1; i <= 2 * n; i ++)
	{
		int x, y; cin >> x >> y;
		g[i].push_back(x + 2 * n), g[i].push_back(x + 3 * n);
		g[i].push_back(y + 2 * n), g[i].push_back(y + 3 * n);
	}
	for(int i = 1; i <= 2 * n; i ++)
	{
		if(dfs(i, i)) ans ++;
	}
	cout << ans << endl;
}

P2756 飞行员配对方案问题

思路

和上一题差不多,都很模板。

输出方案的话我们在dfs中已经记录了,直接输出即可

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n, m, vis[10005], ans, rec[10005];
vector<int> g[4005];
int dfs(int u, int t)
{
	if(vis[u] == t) return 0;
	vis[u] = t;
	for(int v : g[u])
	{
		if(rec[v] == 0 || dfs(rec[v], t))
		{
			rec[v] = u;
			return 1;
		}
	}
	return 0;
}
signed main()
{
	cin >> m >> n;
	while(1)
	{
		int x, y; cin >> x >> y;
		if(x == -1 || y == -1 ) break;
		g[x].push_back(y);
		g[y].push_back(x);
	}
	for(int i = 1; i <= m; i ++)
	{
		if(dfs(i, i)) ans ++;
	}
	cout << ans << endl;
	for(int i = m + 1; i <= n; i ++)
	{
		if(rec[i] != 0) cout << i << " " << rec[i] << endl;
	}
}

0724

贪心世界第一好玩!

CF1601D Difficult Mountain

神秘贪心

思路

发现当 \(a < d\) 时没有任何影响,所以将所有 \(a < d\) 的登山者全部放到前面。

然后以 \(\max(a_{i},s_{i})\) 为第一关键字, \(s_{i}\) 为第二关键字从小到大排序。

然后依次枚举,能选就选,就是最优答案。

证明:

\(\max(a,s)=a/s\) 分类讨论。

  • \(\max_{i}=a_{i},\max_{j}=a_{j}\) ,不妨设 \(a_{i}<a_{j}\) ,如果 \(j\) 先上, \(i\) 一定上不去,所以不如让 \(i\) 先去试试。

  • \(\max_{i}=a_{i},\max_{j}=s_{j}\) ,如果 \(a_{i}>s_{j}\) 则同上, \(i\) 先上, \(j\) 一定上不去,所以不如让 \(j\) 先去试试。 如果 \(a_{i}\le s_{j}\) ,那么 \(i\) 先上对 \(j\) 显然不会有影响。

  • \(\max_{i}=s_{i},\max_{j}=s_{j}\) ,不妨设 \(s_{i}<s_{j}\) ,如果 \(s_{i}<a_{j}\) ,显然是要 \(i\) 先去试试,如果 \(s_{i}>a_{j}\) ,先让 \(i\) 上,对 \(j\) 显然没有影响。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5e5 + 5;
int ans, d, nn, n;
struct node
{
    int s, a;
}a[N];
bool cmp(node a, node b)
{
    if(max(a.a, a.s) == max(b.a, b.s))
    {
        return a.s < b.s;
    }
    return max(a.a, a.s) < max(b.a, b.s);
}
signed main()
{
    cin >> n >> d;
    for(int i = 1; i <= n; i ++) cin >> a[i].s >> a[i].a;
    sort(a + 1, a + n + 1, cmp);
    for(int i = 1; i <= n; i ++)
    {
        if(a[i].s >= d)
        {
            ans ++;
            d = max(d, a[i].a);
            // cout << "qwq: "<< a[i].s << " " << a[i].a << endl;
        }
    }
    cout << ans << endl;
}

AT_arc121_d [ARC121D] 1 or 2

思路

当只能吃两颗糖时,用最小的匹配最大的、次小的匹配次大的、第三小的匹配第三大的……一定是最优的。

原理小学生都会,这里就不证明了。

那么考虑只能吃一颗的情况。

考虑在糖果中加入美味度为 \(0\) 的糖果,那么就可以像只吃两颗糖一样匹配了。

代码要点:

  • 美味度为 \(0\) 的糖果需要加入序列中并重新排序(因为会有负数的糖果qwq)。
  • 可能糖果总数不是偶数,那么会出现没有匹配上的糖果,需要单独处理。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int inf = 1e18;
const int N = 5e5 + 5;
int n, a[N], minn, maxx, ans = inf;
signed main()
{
    cin >> n;
    for(int i = 1; i <= n; i ++) cin >> a[i];
    for(int i = n % 2; i <= n; i += 2)
    {
        sort(a + 1, a + n + 1);
        minn = inf, maxx = -inf;
        int l = 1, r = n;
        while(l < r)
        {
            // cout <<"qwq: "<< l << " " << r << " " << a[l]<< " " << a[r] << endl;
            minn = min(minn, a[l] + a[r]);
            maxx = max(maxx, a[l] + a[r]);
            // cout << minn << " " << maxx << endl;
            l ++, r --;
        }
        if(l == r)
        {
            minn = min(minn, a[r]);
            maxx = max(maxx, a[r]);
        }
        ans = min(ans, maxx - minn);
        n ++;
        a[n]= 0;
    }
    cout << ans << endl;
}

P8162 [JOI 2022 Final] 让我们赢得选举 / Let's Win the Election

思路

显然,对于每个州,演讲时间只有 \(0\)\(a_{i}\)\(b_{i}\) 三种情况。

定义演讲时间为 \(a_{i}\) 的州为 \(\operatorname{A}\) 类州(即只获得选票),演讲时间为 \(b_{i}\) 的州为 \(\operatorname{B}\) 类州(即获得协作者),演讲时间为 \(0\) 的州为 \(\operatorname{C}\) 类州(即不去演讲)。

先去演讲 \(\operatorname{B}\) 类州,再去演讲 \(\operatorname{A}\) 类州是更优的。

所以我们按照 \(b_{i}\) 排序。

我们枚举 \(b_{i}\) ,进行 \(\operatorname{dp}\)

由于是按照 \(b_{i}\) 升序排列的,那么如果 \(\operatorname{B}\) 类州之前存在 \(\operatorname{C}\) 类州,让该C类州成为 \(\operatorname{B}\) 类州,该 \(\operatorname{B}\) 类州成为 \(\operatorname{C}\) 类州是更优的。

那么序列的前半部分只有 \(\operatorname{A}\) 类州或 \(\operatorname{B}\) 类州,后半部分只有 \(\operatorname{A}\) 类州或 \(\operatorname{C}\) 类州。

所以定义 \(dp_{i,j}\) 表示枚举到第 \(i\) 个州,选了 \(j\)\(\operatorname{B}\) 类州的最少时间。

枚举 \(\operatorname{B}\) 类州的个数 \(\operatorname{sum}\),然后进行 \(\operatorname{dp}\)

\(\operatorname{dp}\)\(\operatorname{sum}\) 后停止,计算剩下的州中最小的 \(\operatorname{A}\) 类州的时间。

设后 \(x\) 个州里,\(a_{i}\) 最小的 \(y\) 个州的 \(a_{i}\) 之和为 \(w(x,y)\)

那么答案就是 \(\min_{i = sum}^{k}dp_{i,sum} + w(n - i,k - i)\)

\(\operatorname{dp}\) 的时间复杂度是 \(O(n^{2})\),由于要枚举 \(\operatorname{B}\) 类州的个数,所以总时间复杂度 \(O(n^{3})\)

点击查看代码
#include<bits/stdc++.h>
#define ldb double
using namespace std;
const int inf = 1e9;
const int N = 505;

struct node
{
    int a, b;
}a[N], aa[N];
bool cmp(node a, node b)
{
    return a.b < b.b;
}
bool cmp1(node a, node b)
{
    return a.a < b.a;
}

int n, k;
ldb dp[N][N], f[N][N], ans = inf;

signed main()
{
    cin >> n >> k;
    for(int i = 1; i <= n; i ++)
    {
        cin >> a[i].a >> a[i].b;
        if(a[i].b == -1) a[i].b = inf;
        aa[i].a = a[i].a, aa[i].b = a[i].b;
    }
    sort(aa + 1, aa + n + 1, cmp);
    sort(a + 1, a + n + 1, cmp);
    for(int i = n; i >= 1; i --)//预处理
    {
        sort(aa + i, aa + n + 1, cmp1);
        for(int j = i; j <= n; j ++) f[i][j - i + 1] = f[i][j - i] + aa[j].a;
    }
    for(int i = 0; i < k; i ++)//枚举找几个人
    {
        for (int l = 0; l <= k; l++) for (int j = 0; j <= i; j++) dp[l][j] = inf;
        dp[0][0] = 0;
        for(int j = 1; j <= k; j ++)
        {
            dp[j][0] = dp[j - 1][0] + a[j].a * 1.0 / (i + 1);
            for(int l = 1; l <= i; l ++)
            {
                dp[j][l] = min(dp[j - 1][l - 1] + a[j].b * 1.0 / l, dp[j - 1][l] + a[j].a * 1.0 / (i + 1));
            }
        }
        for(int j = 0; j <= k; j ++)
        {
            ans = min(ans, dp[j][i] + f[j + 1][k - j] * 1.0 / (i + 1));
        }
    }
    printf("%.5lf", ans);
}

0726

P2587 [ZJOI2008] 泡泡堂

这题真的只有绿吗。

思路

考虑如下贪心策略:

  • 如果当前我方最小打得过对方最小,那么就打。
  • 如果当前我方最大打得过对方最大,那么就打。(因为下面的操作是将对方最大的耗掉,我们既然能打过,那么为什么要耗掉呢?)。
  • 我方最小和对方最大玉碎,因为对方最大太强了,谁都打不过,最优的情况是用我方最小耗掉他。

我方最坏情况即对方最优情况,不用再分类一遍了。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int n, a[N], b[N];
int work(int a[], int b[])
{
	int res = 0;
	int l = 1, r = n, mi = 1, ma = n;
	while(l <= r && mi <= ma)
	{
		if(a[mi] > b[l])
		{
			mi ++, l ++;
			res += 2;
		}
		else if(a[ma] > b[r])
		{
			ma --, r --;
			res += 2;
		}
		else
		{
			if(a[mi] == b[r]) res ++;
			mi ++, r --;
		}
	}
	return res;
}
signed main()
{
	cin >> n;
	for(int i = 1; i <= n; i ++) cin >> a[i];
	for(int i = 1; i <= n; i ++) cin >> b[i];
	sort(a + 1, a  + n + 1);
	sort(b + 1, b + n + 1);
	cout << work(a, b) << " " << n * 2 - work(b, a) << endl;
}

P6927 [ICPC 2016 WF] Swap Space

思路

考虑将硬盘分成两类,然后排个序,贪心即可

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n, ans, sum;
struct node
{
	int a, b;
};
vector<node> v1, v2;
bool cmp1(node a, node b) {return a.a < b.a;}
bool cmp2(node a, node b) {return a.b > b.b;}
signed main()
{
	cin >> n;
	for(int i = 1; i <= n; i ++)
	{
		int a, b; cin >> a >> b;
		if(a < b) v1.push_back({a, b});
		else v2.push_back({a, b});
	}
	sort(v1.begin(), v1.end(), cmp1);
	sort(v2.begin(), v2.end(), cmp2);
	for(int i = 0; i < v1.size(); i ++)
	{
		ans = min(ans, sum - v1[i].a);
		sum += (v1[i].b - v1[i].a);
	}
	for(int i = 0; i < v2.size(); i ++)
	{
		ans = min(ans, sum - v2[i].a);
		sum += (v2[i].b - v2[i].a);
	}
	cout << -ans << endl;
}

P8113 [Cnoi2021] 自我主义的平衡者

思路

考虑贪心。

不难发现,\(a_i\) 从小到大排序时,平均分是最高的。\(a_i\) 从大到小排序时,平均分是最低的。

证明:

\(a_i\) 从小到大排序:

  • 当前平均分还很低时,前面的 \(a_i\) 更愿意打满分,一旦打了满分,平均分就会被抬高。
  • 如果平均分已经超过他们的心理预期,他们就会打 \(0\) 分,且拉低平均分,方便后面的人打满分。
  • 因为数组已经升序排列,\(a_i\) 会越来越大。前面有人打 \(0\) 分压低平均分后,后面的 \(a_i\) 就更容易打满分。

\(a_i\) 从大到小排序:

  • 前面的 \(a_i\) 会将平均分拉的很高,那么后面的人就会几乎全部打 \(0\) 分,将平均分拉的很低。
  • 因为满分 \(m\) 是最高分,一旦前期把平均分抬高,后面再打 \(0\) 分也只能缓慢拉低,但很难再降到一个让挑剔者重新打满分的水平。这样满分就基本被“锁死”在前几个\(a_i\) 上,后续的人全在打 \(0\) 分,总分自然最小。

警示后人:

  • 小心精度问题。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
int ans, a[100005], n, m, ans1;
signed main()
{
	cin >> n >> m;
	for(int i = 1; i <= n; i ++)
	{
		cin >> a[i];
	}
	sort(a + 1, a + n + 1);
	for(int i = 1; i <= n; i ++)
	{
		if(ans <= a[i] * (i - 1))
		{
			ans += m;
		}
	}
	for(int i = n; i >= 1; i --)
	{
		if(ans1 <= a[i] * (n - i))
		{
			ans1 += m;
		}
	}
	double cnt1 = ans * 1.0 / n, cnt2 = ans1 * 1.0 / n;
	printf("%.2lf %.2lf\n", cnt1, cnt2);
}

P1456 Monkey King

思路

直接启发式合并。

\(n\) 个优先队列,除了合并时小的合并到大的以外,其他的和暴力差不多。

注意需要开一个数组存储一个元素当前在的优先队列的编号。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 5;
struct node
{
	int x, id;
	bool operator<(const node qwq) const
	{
		return qwq.x > x;
	}
};
priority_queue<node> q[N];
int n, cur[N], m, a[N];
signed main()
{
	cin >> n;
	for(int i = 1; i <= n; i ++)
	{
		cin >> a[i];
		q[i].push({a[i], i});
		cur[i] = i;
	}
	cin >> m;
	while(m --)
	{
		int x, y; cin >> x >> y;
		int u = cur[x], v = cur[y];
		if(u == v)
		{
			cout << -1 << endl;
			continue;
		}
		if(v < u) swap(u, v);
		q[u].push({q[u].top().x / 2, q[u].top().id});
		q[u].pop();
		q[v].push({q[v].top().x / 2, q[v].top().id});
		q[v].pop();
		if(u <= v)
		{
			cur[x] = v;
			while(!q[u].empty())
			{
				q[v].push(q[u].top());
				cur[q[u].top().id] = v;
				q[u].pop();
			}
		}
		cout << q[v].top().x << endl;
	}
}

P1902 刺杀大使

思路

二分答案。

二分判断就dfs判断连通性就好了,如果一个点的值大于我们二分的那个答案,则不能走。

注意到dfs不能回溯,因为这个点一旦走不通,则没必要再走一遍。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n, m, a[1005][1005], flag, vis[1005][1005];
int fx[4] = {0, 0, 1, -1}, fy[4] = {1, -1, 0, 0};
void dfs(int x, int y, int val)
{
	if(x == n)
	{
		flag = 1;
		return ;
	}
	if(flag == 1) return ;
	for(int i = 0; i < 4; i ++)
	{
		int xx = x + fx[i], yy = y + fy[i];
		if(!vis[xx][yy] && xx >= 1 && yy >= 1 && xx <= n && yy <= m && a[xx][yy] <= val)
		{
			vis[x][y] = 1;
			dfs(xx, yy, val);
			// vis[x][y] = 0;
		}
	}
}
bool check(int x)
{
	flag = 0;
	memset(vis, 0, sizeof(vis));
	dfs(1, 1, x);
	return flag;
}
signed main()
{
	cin >> n >> m;
	for(int i = 1; i <=  n; i ++)
	{
		for(int j = 1; j <= m; j ++)
		{
			cin >> a[i][j];
		}
	}
	int l = 0, r = 1000;
	while(l <= r)
	{
		int mid = l + r >> 1;
		if(check(mid)) r = mid - 1;
		else l = mid + 1;
	}
	cout << r + 1 << endl;
}

0729

P3514 [POI 2011] LIZ-Lollipop

很巧妙的思维题。

题意

给一个长为 \(n\) 且只有 \(1\)\(2\) 的序列,\(m\) 次询问,每次询问有没有一个子串的和为 \(x\)

\(1 \le n, m \le 10 ^ 6\)

思路

性质1:每个答案必定在最大的总和为奇/偶数的范围内。

证明:

考虑当前区间 \([l, r]\) 总和为 \(x\),求区间总和为 \(x - 2\) 的区间。

  • \(a_l\)\(2\) 则答案为 \([l + 1, r]\)
  • \(a_r\)\(2\) 则答案为 \([l, r - 1]\)
  • 如果上面两种情况都不满足,则 \(a_l = a_r = 1\) 答案为 \([l + 1, r - 1]\)

则我们分别预处理出奇数和偶数的最大区间和的区间,然后离线给答案从大到小排个序,双指针求出答案即可。

无解的情况就是 \(x\) 大于最大区间和。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int n, m, a[N], l, r, ma1, ma2, ll, rr, b[N];
struct Ans
{
    int l, r;
}ans[N];
vector<Ans> q1, q2;
bool cmp(Ans a, Ans b)
{
    return a.l > b.l;
}
string s;

signed main()
{
    cin >> n >> m;
    cin >> s;
    for(int i = 1; i <= m; i ++)
    {
        int x;
        scanf("%d", &x);
        if(x % 2 == 0) q1.push_back({x, i});
        else q2.push_back({x, i});
    }
    for(int i = 0; i < n; i ++)
    {
        a[i + 1] = (s[i] == 'T') + 1 + a[i];
        b[i + 1] = (s[i] == 'T') + 1;
        // cout << a[i + 1] << " " << b[i + 1] << endl;
    }
    for(int i = 1; i <= n; i ++)
    {
        int sum = a[i] - a[0], sum2 = a[n] - a[i - 1];
        if(sum % 2 == 0)
        {
            if(sum > ma1) l = 1, r = i, ma1 = sum;
        }
        else
        {
            if(sum > ma2) ll = 1, rr = i, ma2 = sum;
        }
        if(sum2 % 2 == 0)
        {
            if(sum2 > ma1) l = i, r = n, ma1 = sum2;
        }
        else
        {
            if(sum2 > ma2) ll = i, rr = n, ma2 = sum2;
        }
    }
    // cout << "qwq: " << l << " " << r << endl;
    // cout << "awa: " << ll << " " << rr << endl;
    sort(q1.begin(), q1.end(), cmp);
    sort(q2.begin(), q2.end(), cmp);
    for(Ans u : q1)
    {
        int v = u.l;
        if(a[r] - a[l - 1] < v)
        {
            ans[u.r].l = -1;
            ans[u.r].r = -1;
            continue;
        }
        while(a[r] - a[l - 1] != v)
        {
            if(b[r] == 2) r --;
            else if(b[l] == 2) l ++;
            else r --, l ++;
        }
        ans[u.r].l = l, ans[u.r].r = r;
    }
    for(Ans u : q2)
    {
        int v = u.l;
        if(a[rr] - a[ll - 1] < v)
        {
            ans[u.r].l = -1;
            ans[u.r].r = -1;
            continue;
        }
        while(a[rr] - a[ll - 1] != v)
        {
            if(b[rr] == 2) rr --;
            else if(b[ll] == 2) ll ++;
            else rr --, ll ++;
        }
        ans[u.r].l = ll, ans[u.r].r = rr;
    }
    for(int i = 1; i <= m; i ++)
    {
        if(ans[i].l == -1) printf("NIE\n");
        else printf("%d %d\n", ans[i].l, ans[i].r);
    }
}

P2279 [HNOI2003] 消防局的设立

题意

给定一棵有 \(n\) 个点的树。每次操作可以选定一个点,覆盖和这个点距离 \(2\) 以内的所有点(包括这个点),求最小操作次数。

距离的定义为两个点之间的边的数量。

\(n \le 1000\)

思路

看到这个数据时我是有点迷惑的,怀疑这题的算法时间复杂度是 \(O(n ^ 2 \log n)\) 的。

考虑贪心,不难发现,当一个点没有被覆盖的时候,选它的祖父点覆盖一定是最优的(因为它的祖父节点可以确保从它和它的兄弟到它的祖父的祖父全都被覆盖)。

那么我们怎么更高效的利用呢?考虑求出每个点的深度,按深度从大到小处理:

  • 如果这个点已经被覆盖了,则不用考虑。
  • 如果这个点没有祖父,则考虑覆盖父亲节点。
  • 没有父亲则覆盖自己。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n, cov[1005], ans;
vector<int> ve[1005];
int dep[1005];
struct node
{
	vector<int> son;
	int fa;
}g[1005];

void dfs(int u, int fa)
{
	g[u].fa = fa;
	dep[u] = dep[fa] + 1;
	for(int i = 0; i < ve[u].size(); i ++)
	{
		int v = ve[u][i];
		if(v == fa) continue;
		g[u].son.push_back(v);
		dfs(v, u);
	}
}

void col(int u, int sum, int lst)
{
    if(sum > 2) return ;
    cov[u] = 1;
    for(int v : ve[u])
    {
        if(v == lst) continue;
        col(v, sum + 1, u);
    }
}
bool cmp(int a, int b)
{
    return dep[a] > dep[b];
}

signed main()
{
	cin >> n;
	for(int i = 2; i <= n; i ++)
	{
		int x; cin >> x;
		ve[x].push_back(i);
		ve[i].push_back(x);
	}
	dep[0] = -1;
	dfs(1, 0);
	vector<int> id;
	for(int i = 1; i <= n; i++) id.push_back(i);
	sort(id.begin(), id.end(), cmp);
	memset(cov, 0, sizeof(cov));
	ans = 0;
	for(int u : id)
	{
	    if(!cov[u])
	    {
	        ans++;
	        int pos = u;
	        if(g[pos].fa != 0) pos = g[pos].fa;
	        if(g[pos].fa != 0) pos = g[pos].fa;
	        col(pos, 0, 0);
	    }
	}

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

0812

P5521 [yLOI2019] 梅深不见冬

贪心题。

思路

我们需要选择孩子的处理顺序,使得上式中的最大值最小。
考虑两个相邻的孩子 \(a\)\(b\),比较两种顺序的优劣:

先 a 后 b

峰值为 \(max(f[a], w_a + f[b])\)

先 b 后 a

峰值为 \(max(f[b], w_b + f[a])\)

令先 \(a\)\(b\) 不劣于先 \(b\)\(a\),即:
\(max(f[a], w_a + f[b]) ≤ max(f[b], w_b + f[a])\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int n, w[N], ans[N], f[N], val[N];
vector<int> g[N];

void dfs(int u, int fa)
{
    if(g[u].empty())
    {
        val[u] = w[u];
        return ;
    }
    vector<pair<int, int>> vec;
    for(int v : g[u])
    {
        dfs(v, u);
        vec.push_back({val[v], w[v]});
    }
    sort(vec.begin(), vec.end(), [](const pair<int, int> &a, const pair<int, int> &b) {
        int da = a.first - a.second;
        int db = b.first - b.second;
        if(da != db) return da > db;
        return a.first > b.first;
    });
    int sum = 0, maxx = 0;
    for(auto &p : vec)
    {
        int need = p.first, cost = p.second;
        maxx = max(maxx, need + sum);
        sum += cost;
    }
    val[u] = max(maxx, sum + w[u]);
}

signed main()
{
    cin >> n;
    for(int i = 2; i <= n; i ++)
    {
        cin >> f[i];
        g[f[i]].push_back(i);
    }
    for(int i = 1; i <= n; i ++) cin >> w[i];
    dfs(1, 0);
    for(int i = 1; i <= n; i ++)
    {
        cout << val[i] << " ";
    }
    printf("\n");
}
posted @ 2026-08-13 14:53  Vodyanitsa  阅读(3)  评论(0)    收藏  举报