POI - A
POI - A
知识点:
A. 旋转变换坐标公式,Dilworth 定理
B. 贪心
C. 二项式定理,特殊性质,Lucas 定理
D. 阶梯博弈(Staircase Nim)
E. 扫描线
F. 单调队列优化 DP
G. 差分约束,Tarjan 缩点,Floyd 最短路
H. 树上 DP,二分答案
I. 单调栈
J. AC 自动机
A. [POI 1998] Flat Broken Lines (lam)
题意
给定坐标系上的
对于
思路
夹角区间
旋转变换坐标公式:将点
绕原点旋转 角,所得的点为 。
这里
由于系数不影响相对位置,所以我们最后记录的点就是
观察夹角区间,发现如果把点按
于是我们先将点按横坐标排序,然后计算
Dilworth 定理:
一个数列的最少单调不上升子序列个数等于最长单调上升子序列长度;
一个数列的最少单调不下降子序列个数等于最长单调下降子序列长度。
有了这个定理,问题就变成了我们熟悉的求最长下降子序列长度了。
代码
#include <iostream>
#include <algorithm>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
using namespace std;
const int N = 3e4 + 10;
int n, ans, f[N];
struct Point {
int x, y;
friend bool operator<(Point const &p, Point const &q) {
return p.x == q.x ? p.y < q.y : p.x < q.x;
}
} a[N];
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n;
f(i, 1, n) {
int x, y;
cin >> x >> y;
a[i] = (Point){x - y, x + y};
}
sort(a + 1, a + n + 1);
f[ans = 1] = a[1].y;
f(i, 2, n)
if (a[i].y < f[ans]) f[++ans] = a[i].y;
else f[lower_bound(f + 1, f + ans + 1, a[i].y, greater<int>()) - f] = a[i].y;
cout << ans << '\n';
return 0;
}
B. [POI 2003] Chocolate (cze)
题意
有一块
巧克力上共有
我们先沿着三条横线切割,需要
当然,上述简单切法不见得是最优切法,那么怎样切割该块巧克力,花费的代价最少呢?
思路
首先可以发现:如果选择横着切,那么如果当前已经竖着切了
设当前竖着切了
所以我们将
代码
#include <iostream>
#include <algorithm>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
#define int ll
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 2e4 + 10;
int n, m, ans, x, y;
pii a[N];
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> m;
f(i, 1, n - 1) cin >> a[i].first, a[i].second = 0;
f(i, n, n + m - 2) cin >> a[i].first, a[i].second = 1;
n = n + m - 2;
sort(a + 1, a + n + 1, [](pii const &p, pii const &q) { return p.first > q.first; });
f(i, 1, n)
if (a[i].second == 0) ans += a[i].first * (y + 1), ++x;
else ans += a[i].first * (x + 1), ++y;
cout << ans << '\n';
return 0;
}
C. [POI 2003] Trinomial (tro)
题意
求
对于
思路
此题的一个重要特殊性质就是对
我们可以把原式的每项系数尽量变成带
于是我们只需要考虑
所以次数为
代码
#include <iostream>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
#define il inline
#define int long long
using namespace std;
const int MOD = 3;
int f[5][5];
il int C(int n, int m) {
if (n < m) return 0;
return f[n][m];
}
int Lucas(int n, int m) {
if (n < m) return 0;
if (n == m || m == 0) return 1;
if (m == 1) return n;
return Lucas(n / MOD, m / MOD) * C(n % MOD, m % MOD) % MOD;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int k, n, i;
cin >> k;
f[0][0] = 1;
f(i, 1, 2) {
f[i][0] = 1;
f(j, 1, i) f[i][j] = f[i - 1][j - 1] + f[i - 1][j];
}
while (k--) {
cin >> n >> i;
cout << (Lucas(n << 1, i) * (((n * 2 - i) & 1) ? -1 : 1) % MOD + MOD) % MOD << '\n';
}
return 0;
}
D. [POI 2004] Game (gra)
见我的另一篇文章 学习笔记:Nim 游戏。
E. [POI 2001] Goldmine (kop)
题意
平面内有
对于
思路
类似 洛谷 P1502 窗口的星星 的思想。
以每一个点为左下角顶点,画一个
于是答案即为所有矩形
代码
#include <iostream>
#include <algorithm>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
using namespace std;
const int N = 1e5 + 10;
int s, w, n, ans;
struct Q {
int x, sy, ty, c;
Q() {}
Q(int _x, int _sy, int _ty, int _c): x(_x), sy(_sy), ty(_ty), c(_c) {}
friend bool operator<(Q const &_, Q const __) {
return _.x == __.x ? _.c > __.c : _.x < __.x;
}
} q[N];
struct SegTree {
#define lson (u << 1)
#define rson (u << 1 | 1)
struct Node {
int l, r, add, maxx;
Node() {}
Node(int _l, int _r): l(_l), r(_r), add(0), maxx(0) {}
} tr[N << 2];
void pushup(int u) {
tr[u].maxx = max(tr[lson].maxx, tr[rson].maxx);
return;
}
void pushdown(int u) {
if (tr[u].add) {
Node &rt = tr[u], &ls = tr[lson], &rs = tr[rson];
ls.add += rt.add, ls.maxx += rt.add;
rs.add += rt.add, rs.maxx += rt.add;
rt.add = 0;
}
return;
}
void build(int u, int l, int r) {
tr[u] = Node(l, r);
if (l == r) return;
int mid = (l + r) >> 1;
build(lson, l, mid);
build(rson, mid + 1, r);
// pushup(u);
return;
}
void modify(int u, int l, int r, int x) {
if (l <= tr[u].l && tr[u].r <= r) {
tr[u].maxx += x, tr[u].add += x;
return;
}
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
if (l <= mid) modify(lson, l, r, x);
if (r > mid) modify(rson, l, r, x);
pushup(u);
return;
}
} t;
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> s >> w >> n;
f(i, 1, n) {
int x, y;
cin >> x >> y;
x += 30001, y += 30001;
q[(i << 1) - 1] = Q(x, y, y + w, 1);
q[i << 1] = Q(x + s, y, y + w, -1);
}
n <<= 1;
sort(q + 1, q + n + 1);
t.build(1, 1, 60001);
f(i, 1, n) {
ans = max(ans, t.tr[1].maxx);
t.modify(1, q[i].sy, q[i].ty, q[i].c);
}
cout << ans << '\n';
return 0;
}
F. [POI 2014] Little Bird (pta)
题意
有
当第
如果一只鸟飞到一颗高度大于等于当前树的树,那么它的劳累值会增加
请你求出每只鸟最小的劳累值。
思路
其实
设
这道题的关键就在于
我们考虑用单调队列维护这个最小值。
设队尾为
原因是
代码
#include <iostream>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
using namespace std;
const int N = 1e6 + 10;
int n, d[N], Q, k, f[N];
int q[N], h, t;
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n;
f(i, 1, n) cin >> d[i];
cin >> Q;
while (Q--) {
cin >> k;
h = t = 1;
q[h] = 1;
f[1] = 0;
f(i, 2, n) {
while (h <= t && q[h] < i - k) ++h;
f[i] = f[q[h]] + (d[q[h]] <= d[i]);
while (h <= t && (f[q[t]] > f[i] || (f[q[t]] == f[i] && d[i] >= d[q[t]]))) --t;
q[++t] = i;
}
cout << f[n] << '\n';
}
return 0;
}
G. [POI 2012] Festival (fes)
题意
越野赛有
求所有参赛者一共最多可以达到多少种不同的成绩。
对于
思路
考虑差分约束,设
首先无解情况就是图中有负环的情况,可以用 Floyd 算法判断:如果做完 Floyd 之后存在
那么如何求出答案呢?首先,对于任何一个 SCC,所有人的时间都加上同一个值对相互间的关系没有影响,而两个 SCC 之间的边一定是
于是考虑单独一个 SCC。求最多的不同的成绩数,即是求最长的最短路长度再加一。先用 Floyd 处理所有最短路,然后枚举求出最大值,累加进答案。
代码
洛谷上开了 O2 优化才过的。
#include <iostream>
#include <cstring>
#include <vector>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
#define pb push_back
using namespace std;
typedef vector<int> vi;
const int N = 610, M = 1e5 + 10;
int n, m1, m2, ans, x, y;
int dis[N][N];
vi e[N];
inline void add(int u, int v, int w) {
e[u].pb(v);
dis[u][v] = min(dis[u][v], w);
return;
}
int dfn[N], low[N], tot;
int st[N], top;
vector<vi> scc;
bool inStack[N];
void Tarjan(int u) {
dfn[u] = low[u] = ++tot;
inStack[st[++top] = u] = true;
for (auto v: e[u])
if (!dfn[v]) Tarjan(v), low[u] = min(low[u], low[v]);
else if (inStack[v]) low[u] = min(low[u], dfn[v]);
if (dfn[u] == low[u]) {
scc.pb(vi());
int t;
while (true) {
t = st[top--];
inStack[t] = false;
scc.back().pb(t);
if (t == u) break;
}
}
return;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
memset(dis, 0x3f, sizeof dis);
cin >> n >> m1 >> m2;
f(i, 1, n) dis[i][i] = 0;
f(i, 1, m1) cin >> x >> y, add(y, x, -1), add(x, y, 1);
f(i, 1, m2) cin >> x >> y, add(y, x, 0);
f(i, 1, n) if (!dfn[i]) Tarjan(i);
f(k, 1, n) f(i, 1, n) f(j, 1, n)
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
f(i, 1, n) if (dis[i][i]) return cout << "NIE\n", 0;
for (auto i: scc) {
int maxx = 0;
for (auto u: i) for (auto v: i)
maxx = max(maxx, dis[u][v]);
ans += maxx + 1;
}
cout << ans << '\n';
return 0;
}
H. [POI 2011] Dynamite (dyn)
题意
给定一棵
设一个关键节点
思路
首先我们对答案进行二分。设当前二分的答案为
那么问题转化为:一棵树上选
然后发现这个问题并不容易解决,于是问题还可以进一步转化:一棵树上选
即用最少的点覆盖所有关键节点。
设
设
如果
否则,如果
注意:特判根节点的情况。由于上面没有更优的点可以选了,所以如果还有没被覆盖的关键节点即
代码
#include <iostream>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
#define il inline
using namespace std;
const int N = 3e5 + 10;
const int INF = 0x3f3f3f3f;
int n, m;
bool a[N];
int head[N], cnt;
struct Edge {
int to, nxt;
} e[N << 1];
il void add(int from, int to) {
e[++cnt].to = to, e[cnt].nxt = head[from], head[from] = cnt;
return;
}
int tot;
int f[N], g[N]; //f[i]: 以i为根的子树中最远未覆盖节点距离; g[i]: 以i为根的子树中最近选择节点距离
void dfs(int u, int fa, int limit) {
f[u] = a[u] ? 0 : -INF;
g[u] = INF;
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].to;
if (v == fa) continue;
dfs(v, u, limit);
f[u] = max(f[u], f[v] + 1);
g[u] = min(g[u], g[v] + 1);
}
if (f[u] + g[u] <= limit) f[u] = -INF; //以u为根的子树可以全部覆盖
if (f[u] == limit) ++tot, f[u] = -INF, g[u] = 0; //选择u
return;
}
il bool check(int x) {
tot = 0;
dfs(1, 0, x);
tot += (f[1] >= 0);
return tot <= m;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> m;
f(i, 1, n) cin >> a[i];
f(i, 1, n - 1) {
int u, v;
cin >> u >> v;
add(u, v), add(v, u);
}
int l = -1, r = n;
while (l + 1 < r) {
int mid = (l + r) >> 1;
if (check(mid)) r = mid;
else l = mid;
}
cout << r << '\n';
return 0;
}
I. [POI 2008] Postering (pla)
题意
思路
显然矩形的宽度无用。
我们发现一个“凹”字形需要用三张海报,而“凸”字形却可以减少一张。
所以前面比当前矩形高的所有矩形都是没有用的。
令初始答案为
具体来说,我们维护一个单调栈,如果栈顶矩形比当前矩形高,那么将栈顶弹出;如果栈顶矩形与当前矩形等高,那么答案减一。
代码
#include <iostream>
#include <stack>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
using namespace std;
int n, ans;
stack<int> st;
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n;
ans = n;
f(i, 1, n) {
int h, w;
cin >> w >> h;
while (!st.empty() && st.top() >= h) {
if (st.top() == h) --ans;
st.pop();
}
st.push(h);
}
cout << ans << '\n';
return 0;
}
J. [POI 2000] Viruses (wir)
TO DO...
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端