We love POI

[POI 2012] BON-Vouchers

考虑从倍数出发,如果值域是 \(m\)\(mH(m)\),是 \(O(m \ln)\) 的。

那么本题是否可以直接做?答案是不行,对于一个倍数 \(k\),之前 \(k\) 的倍数取到的数可能再次枚举到,无法拿走(不计入贡献),因此复杂度是不对的。

我们只需要去除 \(k\) 枚举之前 \(k\) 取到的数即可,这样对于对于 \(k\) 最多枚举 \(\lfloor m/k \rfloor\) 次。

具体实现是记录一下上一个同样的 \(k\) 枚举到几倍了。

[POI 2012] HUR-Warehouse Store

没什么好说的,贪心直接做完。

[POI 2005] SUM-Fibonacci Sums

好题。

考虑如果没有同一位 \(a,b\) 都是 \(1\) 是好处理的。

于是我们考虑如何处理加完之后是 \(2\)

首先考虑如果是 \(2\),如何变换使得至少这一位不是 \(2\),我们设 \(now\) 为当前和串。容易发现 \(now_{i} -2 \to now_{i},now_{i-2} +1 \to now_{i-2},now_{i+1}+1 \to now_{i+1}\) 是一种方法。

同时,这可能导致 \(3\) 的产生,先不管。

对于一个 \(2\) 的处理,我们有两种选择,一种是让左边继续合法,不管右边,反之为另一种。应该选择后者,因为 \(now_{i+1}\) 会加一,如果从 \(i\) 到最右边都不存在能够进位的数,可以直接作用且不会使右边产生 \(2\)

现在确定目标,我们希望从右到左,调整使得全体合法,现在开始讨论。

  • \(now_{i}\)\(0\)\(i\) 减一下一位。

  • \(now_{i}\)\(1\),将 \(i\) 及后进位(可以证明不会产生 \(2\))。

  • \(now_{i}\)\(2\),若 \(now_{i+1}\)\(1\),对 \(i\) 及后进位(可以证明不会产生 \(2\))。否则对 \(i\) 作用变换后对 \(i+1\) 及后进位。

  • \(now_{i}\)\(3\),若 \(now_{i+1}\)\(1\),对 \(i\) 及后进位,然后转化为 \(now_{i}=2\) 的第二种情况。否则对 \(i\) 作用变换后对 \(i+1\) 及后进位,之后转化为情况 \(now_{i}=1\)

OK!做完了,为了方便我们可以寻找这些讨论的共通性,若 \(now_{i},now_{i+1}\) 其中一个为 \(0\),那么 \(i\) 进位操作不会影响任何东西。我们现在处理完了 \(1,2.1\) 情况,考虑 \(2.2,3.1,3.2\) 情况,特点是仍然 \(now_{i} \le 2\),之后做共通的操作,也就是对 \(i\) 作用变换后对 \(i+1\) 后进位,此时 \(3.2\) 还需要一个 \(i\) 及后进位。总结:

  • \(i\) 及后进位。

  • \(now_{i} \le 2\),对 \(i\) 作用变换后对 \(i+1\) 后进位。

  • \(i\) 及后进位。

注意 \(i<3\) 要特判。

// Problem: P3424 [POI 2005] SUM-Fibonacci Sums
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3424
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
#define int long long
#define upp(a, x, y) for (int a = x; a <= y; a++)
#define dww(a, x, y) for (int a = x; a >= y; a--)
#define pb(x) push_back(x)
#define endl '\n'
#define x first
#define y second
#define PII pair<int, int>
using namespace std;
const int N = 1e6 + 10;
int now[N];
int n;
void flush(int x) {
    while (now[x] && now[x + 1]) now[x]--, now[x + 1]--, now[x + 2]++, x += 2;
}
void solve() {
    int x;
    cin >> x;
    n = x;
    upp(i, 1, x) cin >> now[i];
    cin >> x;
    n = max(n, x);
    upp(i, 1, x) {
        int c;
        cin >> c;
        now[i] += c;
    }
    dww(i, n, 1) {
        flush(i);
        if (now[i] >= 2) {
            now[i] -= 2, now[i + 1]++;
            if (i >= 2) now[max(i - 2, (int)1)]++;
            flush(i + 1);
        }
        flush(i);
    }
    dww(i, n + 2, 1) if (now[i]) {
        cout << (n = i) << ' ';
        break;
    }
    upp(i, 1, n) cout << now[i] << ' ';
    cout << endl;
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int tt = 1;
    while (tt--) solve();
    return 0;
}

[POI 2007] BIU-Offices

求补图的连通块个数。

先讲一个并查集不动脑子的做法,考虑用并查集维护连通块个数,原图中连一条边 \((x,y)\) 相当于将 \(x\) 要连的边,其中一段隔开分成两段。所以我们最多会处理 \(O(n+m)\) 次,形如合并 \(x\)\([l,r]\) 这段区间里面的所有点的操作。最后查询有多少个点祖先是自己。这个是萌萌哒。可以 \(O(n\log n + m)\) 的做(忽略反阿克曼函数)。

考虑遍历补图,那么是 \(O(n^2)\) 的。但是这个图太完全了,肯定有很多的边没有用。

考虑如果在朴图里,\(x,y\) 已经属于同一个连通块,那么 \((x,y)\) 的边就是没有用的,我们实在是不应该遍历到。

我们可以建立一个表,表示目前没有进入连通块的点,对于所有 \(x\) 连接到的点,打上标记,那么所有表中没有标记的点就会加入连通块,最后清理标记。

至于这个表要支持随机删除,顺序遍历。无疑是链表了。

对于一条边,让一个点在一轮被遍历一次,打一次标记,所以总复杂度是 \(O(m)\) 的。

每个点最多删一次,清理一次标记,所以总复杂度是 \(O(n)\) 的。

时间复杂度 \(O(n+m)\)

具体实现可以使用队列。

[POI 2007] MEG-Megalopolis

树上查询到 \(1\) 点路径上的边权和,改变一条边的权值。

转化一下,查询一个点的点权,将一棵子树里每个点的点权加一。

变成 dfn 序之后区间修改单点查询即可。

[POI 2008] POD-Subdivision of Kingdom

注意到 \(26\choose 13\) 大约是 1e7 的。直接搜索第一个集合的点,搜索的同时记录边的数量,可以 \(n/2\) 的记录。这个时候发现只能记录第一个集合里面的。没关系,我们将选的点的状态存起来,下次如果遇到搜到的集合是这个集合的补集的时候在合并信息记录答案就行了。时间复杂度 \(O({n\choose n/2}n)\)

[POI 2009] KON-Ticket Inspector

考虑分段 DP,然后从大到小枚举 \(j\),同时更新转移代价即可 \(O(n^2k)\)

posted @ 2025-09-03 19:49  PM_pro  阅读(14)  评论(0)    收藏  举报