2026-08-21 hetao1733837 的刷题记录
LGP3573 POI 2014 RAJ-Rally
原题链接:POI 2014 RAJ-Rally
分析
由于是一个 有向无环图 ,所以,我们考虑进行 拓扑排序 。
排序之后,我们发现,对于一条路径 ( x , y ) (x,y) (x,y),必有 x < y x<y x<y,且对于这条路径上的点 z z z,其只可能被经历一次。
我们处理 ( x , y ) (x,y) (x,y) 的最长路,即正反两遍拓扑排序 + + +DP,即为到 x x x 的距离、到 y y y 的距离之和加一。
强制经过一条路径意味着这条路径上的所有点都可以被删除,枚举每条边,线段树维护拓扑区间 max \max max。而对于不跨过节点的路径,按照之前 DP 直接查找即可。
正解
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 500005, M = 1000005;
int n, m;
vector<int> e[N], g[N];
int deg[N];
int topo[N], ncnt;
int dise[N], disg[N];
struct segtree{
int tot;
struct node{
int sum, ls, rs;
}tr[N << 2];
void pushup(int p){
tr[p].sum = tr[tr[p].ls].sum + tr[tr[p].rs].sum;
}
void modify(int &p, int l, int r, int s, int val){
if (!p)
p = ++tot;
if (l == r){
tr[p].sum += val;
return ;
}
int mid = (l + r) >> 1;
if (s <= mid)
modify(tr[p].ls, l, mid, s, val);
else
modify(tr[p].rs, mid + 1, r, s, val);
pushup(p);
}
int query(int p, int l, int r){
if (l == r){
return l;
}
int mid = (l + r) >> 1;
if (tr[tr[p].rs].sum)
return query(tr[p].rs, mid + 1, r);
else
return query(tr[p].ls, l, mid);
}
}T;
int q[N];
void toposort(){
int l = 1, r = 0;
for (int i = 1; i <= n; i++){
if (!deg[i]){
topo[++ncnt] = i;
q[++r] = i;
}
}
while (l <= r){
int u = q[l++];
for (auto v : e[u]){
deg[v]--;
if (!deg[v]){
q[++r] = v;
topo[++ncnt] = v;
}
}
}
}
void getdis(){
for (int i = 1; i <= n; i++){
int u = topo[i];
for (auto v : e[u]){
dise[v] = max(dise[v], dise[u] + 1);
}
}
for (int i = n; i >= 1; i--){
int u = topo[i];
for (auto v : g[u]){
disg[v] = max(disg[v], disg[u] + 1);
}
}
}
int rt;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 1, a, b; i <= m; i++){
cin >> a >> b;
e[a].push_back(b);
deg[b]++;
g[b].push_back(a);
}
toposort();
getdis();
T.tot = 0;
int mx, id;
int ans = 0x3f3f3f3f3f3f3f3f;
for (int i = 1; i <= n; i++){
T.modify(rt, 0, n, disg[i], 1);
}
for (int i = 1; i <= n; i++){
int u = topo[i];
T.modify(rt, 0, n, disg[u], -1);
for (auto v : g[u]){
T.modify(rt, 0, n, disg[u] + dise[v] + 1, -1);
}
mx = T.query(rt, 0, n);
if (mx < ans){
ans = mx;
id = u;
}
for (auto v : e[u]){
T.modify(rt, 0, n, dise[u] + disg[v] + 1, 1);
}
T.modify(rt, 0, n, dise[u], 1);
}
cout << id << " " << ans;
}
LGP15038 「chaynOI R2 T3」合并同类项
原题链接:「chaynOI R2 T3」合并同类项
分析
发现每次 fqh 出题都会放一个判断 不劣 的东西。
我们发现如果 x ≤ y x\le y x≤y,那直接合并就行了,所以对 y = min ( x , y ) y=\min(x,y) y=min(x,y) 是不劣的(我可能想到了吗?)
然后,我们进行区间 DP,设 d p l , r dp_{l,r} dpl,r,但是,这样是 O ( n 3 ) O(n^3) O(n3) 的。
我们观察到这题对 k k k 进行了取模,且随机生成,所以,我们至多拥有 n k \dfrac{n}{k} kn 块,所以,我们对于那些模数相等的,可以直接从前面转移,所以,我们的复杂度来到了 O ( n 3 k ) O(\frac{n^3}{k}) O(kn3)。
正解
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 6005, K = 10005;
int n, k, x, y;
int a[N];
int dp[N][N];
int sum[N];
vector<int> pos[N];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> k >> x >> y;
for (int i = 1; i <= n; i++){
cin >> a[i];
sum[i] = (sum[i - 1] + a[i]) % k;
}
for (int i = 1; i <= n; i++){
for (int j = i; j <= n; j++){
if (sum[i - 1] == sum[j]){
pos[j].push_back(i);
}
}
}
for (int i = 1; i <= n; i++){
reverse(pos[i].begin(), pos[i].end());
}
for (int l = n; l >= 1; l--){
dp[l][l] = 0;
for (int r = l + 1; r <= n; r++){
dp[l][r] = dp[l][r - 1] + x;
for (auto v : pos[r]){
if (v > l)
dp[l][r] = min(dp[l][r], dp[l][v - 1] + dp[v][r] + y);
else
break;
}
if (sum[r - 1] == sum[l - 1]){
dp[l][r] = min(dp[l][r], dp[l][r - 1] + y);
}
}
}
cout << dp[1][n];
}
LGP14648 POI 2025/2026 #1 浏览器 / Przeglądarka internetowa
原题链接:POI 2025/2026 #1 浏览器 / Przeglądarka internetowa
分析
建出来字典树,然后树上 DP,设 d p u , 1 / 0 dp_{u,1/0} dpu,1/0 表示是否会到 u u u 节点的最少操作数。转移显然。
为什么要 DP,因为每次 E 都会清空 ,就这么简单......不过贪心似乎也是对的,我就是这么写的,每次分支按照长短贪心。
这是水的/ll
正解
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1000005;
int n;
int ch[N][26], tot;
string s[N];
int f[N][22];
int sz[N], mx[N];
int stk[N], tp;
int de[N];
vector<int> son[N];
int pos[N];
void insert(int x){
int u = 0;
for (int i = 0; i < (int)s[x].length(); i++){
if (!ch[u][s[x][i] - 'a']){
ch[u][s[x][i] - 'a'] = ++tot;
de[tot] = de[u] + 1;
}
u = ch[u][s[x][i] - 'a'];
}
sz[u] = (int)s[x].size();
mx[u] = sz[u];
son[u].push_back(x);
pos[x] = u;
}
void dfs1(int u, int fa){
f[u][0] = fa;
for (int i = 1; i < 22; i++){
f[u][i] = f[f[u][i - 1]][i - 1];
}
for (int i = 0; i < 26; i++){
if (ch[u][i]){
dfs1(ch[u][i], u);
mx[u] = max(mx[u], mx[ch[u][i]]);
}
}
}
bool cmp(int x, int y){
return mx[x] < mx[y];
}
int LCA(int x, int y){
if (de[x] < de[y])
swap(x, y);
for (int i = 19; i >= 0; i--){
if (de[f[x][i]] >= de[y]){
x = f[x][i];
}
}
if (x == y)
return x;
for (int i = 19; i >= 0; i--){
if (f[x][i] != f[y][i]){
x = f[x][i];
y = f[y][i];
}
}
return f[x][0];
}
string ans;
void dfs2(int u){
vector<int> tmp;
for (int i = 0; i < 26; i++){
if (ch[u][i]){
tmp.push_back(ch[u][i]);
}
}
sort(tmp.begin(), tmp.end(), cmp);
for (auto v : son[u]){
stk[++tp] = v;
}
for (auto v : tmp){
dfs2(v);
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++){
cin >> s[i];
insert(i);
}
dfs1(0, 0);
dfs2(0);
for (int i = 1; i <= tp; i++){
if (i == 1){
ans += s[stk[i]];
ans += 'E';
}
else{
int lca = LCA(pos[stk[i - 1]], pos[stk[i]]);
if (de[pos[stk[i]]] <= de[pos[stk[i - 1]]] + de[pos[stk[i]]] - de[lca] * 2 + 1){
ans += s[stk[i]];
ans += 'E';
}
else{
ans += 'T';
for (int j = 1; j <= de[pos[stk[i - 1]]] - de[lca]; j++)
ans += 'B';
for (int j = (int)s[stk[i]].size() - de[pos[stk[i]]] + de[lca]; j < (int)s[stk[i]].length(); j++){
ans += s[stk[i]][j];
}
ans += 'E';
}
}
}
cout << (int)ans.length() << '\n';
cout << ans;
}