LGP17537 JAG 2026 Summer Camp #1 Rabbit, Rabbit, Rabbit
原题链接:JAG 2026 Summer Camp #1 Rabbit, Rabbit, Rabbit
分析
别急,如果说每一次我只让一些兔子向右,不让一些兔子向左,那代价岂不是 0 0 0?
哦,看懂了......好吧,竟然是总共吗?
那么,既然如此,我们就需要......只要把位于同一位置的分开是不是就可以了?
不是,我没看出来在哪贪心......
我需要一个样例解释......
不是,你这个真的有问题啊😭
每一只?哦,那我懂了......
那么,每只兔子最好都移向距离最近的......哦不不不,这个不一定有解。
那么,应该就是先尽可能一个方向,然后再进行一些返回,就可以保证最小了?
所以说,先排序,然后,我们发现,最小距离公式就是:
∑ 1 ≤ i < j ≤ n ∣ d i − d j ∣ \sum\limits_{1\le i<j\le n}|d_i-d_j| 1≤i<j≤n∑∣di−dj∣
其中, d d d 是与目标的距离。
我再也不用 AI 写代码了。
正解
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 300005;
const int inf = 1e18;
int T;
int n, x[N], y[N];
int tmpx[N], tmpy[N];
int calc(){
sort(tmpx + 1, tmpx + n + 1);
for (int i = 2; i <= n; i++)
tmpx[i] -= tmpx[1];
tmpx[1] = 0;
int res = 0;
for (int i = 1; i <= n; i++){
tmpy[i] = tmpy[i - 1] + tmpx[i];
}
for (int i = 2; i <= n; i++){
res += i * tmpx[i] - tmpy[i];
}
return res / 2;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> T;
for (int cs = 1; cs <= T; cs++){
cin >> n;
for (int i = 1; i <= n; i++)
cin >> x[i];
for (int i = 1; i <= n; i++)
cin >> y[i];
sort(x + 1, x + n + 1);
sort(y + 1, y + n + 1);
vector<int> x00, x11;
vector<int> y00, y11;
for (int i = 1; i <= n; i++){
if (x[i] & 1)
x11.push_back(x[i]);
else
x00.push_back(x[i]);
}
for (int i = 1; i <= n; i++){
if (y[i] & 1)
y11.push_back(y[i]);
else
y00.push_back(y[i]);
}
int ans = inf;
if (x00.size() == y00.size() && x11.size() == y11.size()){
int tot = 0;
for (int i = 0; i < (int)x00.size(); i++){
tmpx[++tot] = x00[i] - y00[i];
}
for (int i = 0; i < (int)x11.size(); i++){
tmpx[++tot] = x11[i] - y11[i];
}
ans = min(ans, calc());
}
if (x11.size() == y00.size() && x00.size() == y11.size()){
int tot = 0;
for (int i = 0; i < (int)x00.size(); i++){
tmpx[++tot] = x00[i] - y11[i];
}
for (int i = 0; i < (int)x11.size(); i++){
tmpx[++tot] = x11[i] - y00[i];
}
ans = min(ans, calc());
}
if (ans == inf)
cout << -1 << "\n";
else
cout << ans << "\n";
}
}
LGP14015 ICPC 2024 Nanjing R 生日礼物
分析
思考一下......
我们是不是直接设 d p i , 0 / 1 dp_{i,0/1} dpi,0/1 表示考虑到了第 i i i 位,上一次操作留下来的是 0 / 1 0/1 0/1 的最小长度(这个,我们不用管,就是说,贪心地转移,如果这次可以删除,则立刻删除,从 d p i − 2 dp_{i-2} dpi−2 转移,如果当前是 2 2 2,则可以两个同时进行,直接就做完了,嘻嘻......)
真的假的?先自己写吧。
写到一半上生物课了,剖了一只雄性蛔虫......然后,好像有点分错了,把肠子给整成两截分开展示了,是一个重要的错误......呃......中午就先吃米饭吧,晚上再吃面条,快考试了,考试的时候中午我一般都吃米饭,需要提前一周适应一下。
发现这个直接可以按照奇偶位置贪心......我发现了吗?但是 DP 的复杂度也是对的,所以,我觉得,其实不是很大的问题?
正解
DP 版
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 200005;
int T;
string s;
int a[N];
int dp[N][2][2];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> T;
for (int cs = 1; cs <= T; cs++){
cin >> s;
int top = 0;
for (int i = 0; i < (int)s.length(); i++){
int x = s[i] - '0';
if (top > 0 && x <= 1 && x == a[top]){
top--;
}
else{
top++;
a[top] = x;
}
}
if (top == 0){
cout << 0 << '\n';
continue;
}
for (int i = 0; i <= top; i++){
dp[i][0][0] = -1;
dp[i][0][1] = -1;
dp[i][1][0] = -1;
dp[i][1][1] = -1;
}
if (a[1] == 0 || a[1] == 2){
dp[1][0][0] = 1;
dp[1][0][1] = 1;
}
if (a[1] == 1 || a[1] == 2){
dp[1][1][0] = 1;
dp[1][1][1] = 1;
}
for (int i = 2; i <= top; i++){
if (a[i] == 0 || a[i] == 2){
if (dp[i - 1][1][1] != -1){
dp[i][0][1] = dp[i - 1][1][1] + 1;
}
else if (dp[i - 1][0][0] == 0){
dp[i][0][1] = 1;
}
if (dp[i - 1][0][0] == 0){
dp[i][0][0] = 1;
}
else if (dp[i - 1][1][0] != -1){
dp[i][0][0] = dp[i - 1][1][0] + 1;
}
if (dp[i - 1][0][1] >= 1){
dp[i][1][1] = dp[i - 1][0][1] - 1;
}
if (dp[i - 1][0][0] >= 1){
dp[i][1][0] = dp[i - 1][0][0] - 1;
}
else if (dp[i - 1][0][1] >= 1){
dp[i][1][0] = (dp[i - 1][0][1] & 1) ^ 1;
}
}
if (a[i] == 1 || a[i] == 2){
if (dp[i - 1][1][1] >= 1){
if (dp[i][0][1] == -1){
dp[i][0][1] = dp[i - 1][1][1] - 1;
}
else{
dp[i][0][1] = max(dp[i][0][1], dp[i - 1][1][1] - 1);
}
}
if (dp[i - 1][1][0] >= 1){
if (dp[i][0][0] == -1){
dp[i][0][0] = dp[i - 1][1][0] - 1;
}
else{
dp[i][0][0] = min(dp[i][0][0], dp[i - 1][1][0] - 1);
}
}
else if (dp[i - 1][1][1] >= 1){
int val = (dp[i - 1][1][1] & 1) ^ 1;
if (dp[i][0][0] == -1){
dp[i][0][0] = val;
}
else{
dp[i][0][0] = min(dp[i][0][0], val);
}
}
if (dp[i - 1][0][1] != -1){
if (dp[i][1][1] == -1){
dp[i][1][1] = dp[i - 1][0][1] + 1;
}
else{
dp[i][1][1] = max(dp[i][1][1], dp[i - 1][0][1] + 1);
}
}
else if (dp[i - 1][1][0] == 0){
if (dp[i][1][1] == -1){
dp[i][1][1] = 1;
}
else{
dp[i][1][1] = max(dp[i][1][1], 1LL);
}
}
if (dp[i - 1][1][0] == 0){
if (dp[i][1][0] == -1){
dp[i][1][0] = 1;
}
else{
dp[i][1][0] = min(dp[i][1][0], 1LL);
}
}
else if (dp[i - 1][0][0] != -1){
if (dp[i][1][0] == -1){
dp[i][1][0] = dp[i - 1][0][0] + 1;
}
else{
dp[i][1][0] = min(dp[i][1][0], dp[i - 1][0][0] + 1);
}
}
}
}
int ans = -1;
if (dp[top][0][0] != -1){
if (ans == -1 || dp[top][0][0] < ans){
ans = dp[top][0][0];
}
}
if (dp[top][1][0] != -1){
if (ans == -1 || dp[top][1][0] < ans){
ans = dp[top][1][0];
}
}
cout << ans << '\n';
}
return 0;
}
贪心版
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
int T;
string s;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> T;
for (int cs = 1; cs <= T; cs++){
cin >> s;
int cnt0[2] = {0, 0};
int cnt1[2] = {0, 0};
int cnt2 = 0;
for (int i = 0; i < (int)s.length(); i++){
int p = i % 2;
if (s[i] == '0')
cnt0[p]++;
else if (s[i] == '1')
cnt1[p]++;
else
cnt2++;
}
int rem0 = abs(cnt0[0] - cnt0[1]);
int rem1 = abs(cnt1[0] - cnt1[1]);
int rem = rem0 + rem1;
int ans;
if (cnt2 >= rem)
ans = (cnt2 - rem) % 2;
else
ans = rem - cnt2;
cout << ans << '\n';
}
return 0;
}
QOJ8635 圆
原题链接:圆
分析
拆拆你的贡献!
思考一下具体做法......
就是说,设一个期望 DP 吧!
我们设 d p i , j dp_{i,j} dpi,j 表示有 i i i 个已经被选过,有 j j j 个已经被染黑的期望......
那么怎么转移呢?
我是不是还需要一个有多少白色没有被选择?如果有这个,是不是可以推出来别的?
不妨先将其放入状态,即 d p i , j , k dp_{i,j,k} dpi,j,k 表示已经选了 i i i 个,有 j j j 个已经被染黑,有 k k k 个白色未选的期望。
那么,答案是
a n s = ∑ d p i , n , k ans=\sum dp_{i,n,k} ans=∑dpi,n,k
然而,这个还是不好做......
其实,期望的本质还是组合数学,你需要知道每一个的情况数。
感觉脖子快断了。
本质就是选某一个位置或不选某一个位置,那么,这种 临近 让我们想到了斐波那契数列,所以,我们直接做就行了。
呃......我真的读懂题了吗?
正解
cpp
#include <bits/stdc++.h>
#define int long long
#define mod 998244353
using namespace std;
const int N = 5005;
int n;
int dp[N];
int qpow(int a, int b){
int res = 1;
while (b){
if (b & 1)
res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
int fac[N], inv[N];
int C(int n, int m){
if (n < 0 || m < 0 || n < m)
return 0;
return fac[n] * inv[n - m] % mod * inv[m] % mod;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
fac[0] = 1;
for (int i = 1; i < N; i++){
fac[i] = fac[i - 1] * i % mod;
}
inv[N - 1] = qpow(fac[N - 1], mod - 2);
for (int i = N - 2; i >= 0; i--){
inv[i] = inv[i + 1] * (i + 1) % mod;
}
dp[0] = 1;
int ans = n;
for (int i = 1; i < n; i++){
for (int j = n; j >= 0; j--){
if (j >= 1)
dp[j] = (dp[j] + dp[j - 1]) % mod;
if (j >= 2)
dp[j] = (dp[j] + dp[j - 2]) % mod;
}
ans = (ans + mod - dp[n - i] * qpow(C(n - 1, i - 1), mod - 2) % mod) % mod;
}
cout << ans;
return 0;
}
CF1270G Subset with Zero Sum
分析
我总觉得有什么奇妙的性质......
如果说所有数都取到下界,那么,必然有解;如果所有数取到上界,也必然有解......
然而,这个并不一定是全部上界或下界,所以,即使是前缀和......
别急,你会发现,所有数的取值都是数轴上的滑动的窗口......那又能说明什么呢?
给了,不想了,看看题解......
啥叫图论建模?
天啊,对于 n − i ≤ a i ≤ i − 1 n-i\le a_i\le i-1 n−i≤ai≤i−1,移项可得 1 ≤ i − a i ≤ n 1\le i-a_i\le n 1≤i−ai≤n,所以,将 a i a_i ai 连向 i − a i i-a_i i−ai,则找到环即可。
我严格 O ( n ) O(n) O(n) 还卡我?
正解
cpp
#include <bits/stdc++.h>
// #define int long long
using namespace std;
const int N = 1000005;
int t;
int n, a[N];
int to[N];
bool vis[N];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> t;
for (int cs = 1; cs <= t; cs++){
cin >> n;
// memset(vis, false, sizeof(vis));
for (int i = 1; i <= n; i++){
cin >> a[i];
to[i] = i - a[i];
vis[i] = false;
}
int v = 1;
while (!vis[v]){
vis[v] = true;
v = to[v];
}
vector<int> ans;
ans.push_back(v);
v = to[v];
while (v != ans[0]){
ans.push_back(v);
v = to[v];
}
cout << ans.size() << '\n';
for (auto v : ans)
cout << v << " ";
cout << '\n';
}
}
LGP11801 【MX-X9-T5】『GROI-R3』Star Trip
原题链接:【MX-X9-T5】『GROI-R3』Star Trip
分析
昨天晚上想到了一个 O ( q n 2 ) O(qn^2) O(qn2) 的 DP......
难道他真的有 K r u s k a l Kruskal Kruskal 重构树的性质!
我们可以进行一些处理,进行倍增,考虑从 u u u 出发,走若干步可以到达的点的编号的最大值。
对于依次询问,我们需要知道点 u u u 到 v v v 路径上最大编号的最小值,也就是瓶颈最短路......
呃,啊吧啊吧......
正解
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 400005;
int n, m, q;
vector<int> e[N], tr[N];
struct node{
int u, v, val;
}inp[N];
bool cmp(node tmp1, node tmp2){
return tmp1.val < tmp2.val;
}
int fa[N];
int f[N][22];
int de[N];
int find(int x){
return x == fa[x] ? fa[x] : fa[x] = find(fa[x]);
}
int tot;
int val[N];
int mx[N], pre[N];
int son[N][22];
void merge(int x, int y, int w){
int fx = find(x), fy = find(y);
if (fx == fy)
return ;
val[++tot] = w;
fa[tot] = tot;
fa[fx] = fa[fy] = tot;
tr[tot].push_back(fx);
tr[tot].push_back(fy);
}
void dfs(int u, int pa){
f[u][0] = pa;
de[u] = de[pa] + 1;
for (auto v : tr[u]){
if (v == pa)
continue;
dfs(v, u);
}
}
int LCA(int x, int y){
if (de[x] < de[y])
swap(x, y);
int delta = de[x] - de[y];
for (int i = 20; i >= 0; i--){
if (delta & (1 << i))
x = f[x][i];
}
if (x == y)
return x;
for (int i = 20; i >= 0; i--){
if (f[x][i] != f[y][i]){
x = f[x][i];
y = f[y][i];
}
}
return f[x][0];
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m >> q;
tot = n;
for (int i = 1, u, v; i <= m; i++){
cin >> u >> v;
inp[i].u = u;
inp[i].v = v;
inp[i].val = max(u, v);
e[u].push_back(v);
e[v].push_back(u);
}
sort(inp + 1, inp + m + 1, cmp);
for (int i = 1; i <= n; i++){
fa[i] = i;
}
for (int i = 1; i <= m; i++){
merge(inp[i].u, inp[i].v, inp[i].val);
}
dfs(tot, 0);
for (int i = 1; i <= 20; i++){
for (int u = 1; u <= tot; u++){
f[u][i] = f[f[u][i - 1]][i - 1];
}
}
for (int i = 1; i <= n; i++){
fa[i] = i;
}
for (int i = 1; i <= n; i++){
mx[i] = i;
}
for (int u = 1; u <= n; u++){
for (auto v : e[u]){
if (v < u)
mx[u] = max(mx[u], pre[find(v)]);
else
mx[u] = max(mx[u], v);
}
pre[u] = mx[u];
for (auto v : e[u]){
if (v < u){
int fu = find(u), fv = find(v);
if (fu == fv)
continue;
pre[fv] = max(pre[fv], pre[fu]);
fa[fu] = fv;
}
}
}
for (int i = 1; i <= n; i++)
son[i][0] = mx[i];
for (int i = 1; i <= 20; i++){
for (int u = 1; u <= n; u++){
son[u][i] = son[son[u][i - 1]][i - 1];
}
}
for (int cs = 1, s, t; cs <= q; cs++){
cin >> s >> t;
int tmp = val[LCA(s, t)];
if (s >= tmp){
cout << 1 << "\n";
continue;
}
int ans = 0;
for (int i = 20; i >= 0; i--){
if (son[s][i] < tmp){
ans += (1 << i);
s = son[s][i];
}
}
cout << ans + 2 << "\n";
}
}
LGP15268 「UTOI 1C」Delirium Of Aeons
原题链接:「UTOI 1C」Delirium Of Aeons
分析
仅就贪心的角度,我们对于一个分叉的位置,思考直接开切,这样可以消耗一些 m m m。
剩下的,对于没有兄弟的儿子,那没有办法了,只能对答案进行加了。
天啊,不会是什么 m − n + ∑ u d e g u = 1 m-n+\sum_udeg_u=1 m−n+∑udegu=1。
这个好像不对......但是,为什么还有 T L E TLE TLE?
居然是长链剖分......
我们应该好好想部分分的......我们发现,在链上,除了两端,其他位置都随便做......这提醒我们,虽然我们之前觉得叶子很好做,但是,在链上才是"归宿",所以说,我们进行长链剖分,这样就尽可能减少了相邻为 1 1 1,然后就做完了......
正解
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 200005;
int T;
int n, m;
vector<int> e[N];
int de[N];
int son[N];
int down[N];
int f[N];
vector<int> a;
vector<tuple<int, int, int>> stk1;
vector<pair<int, int>> stk2;
int prevn = 0;
int dfs1(int root){
stk1.clear();
stk1.emplace_back(root, 0, 0);
int far = root;
while (!stk1.empty()){
auto [u, pa, state] = stk1.back();
stk1.pop_back();
if (state == 0){
f[u] = pa;
de[u] = de[pa] + 1;
if (de[u] > de[far])
far = u;
stk1.emplace_back(u, pa, 1);
for (auto v : e[u]){
if (v == pa)
continue;
stk1.emplace_back(v, u, 0);
}
}
else{
int best = 0, bs = 0;
for (auto v : e[u]){
if (v == pa)
continue;
if (down[v] > best){
best = down[v];
bs = v;
}
}
son[u] = bs;
down[u] = max(de[u], best);
}
}
return far;
}
void dfs2(int root){
stk2.clear();
stk2.emplace_back(root, root);
while (!stk2.empty()){
auto [u, tp] = stk2.back();
stk2.pop_back();
int cur = u;
while (cur){
for (auto v : e[cur]){
if (v == f[cur] || v == son[cur])
continue;
stk2.emplace_back(v, v);
}
int nxt = son[cur];
if (!nxt)
a.push_back(de[cur] - de[tp] + 1);
cur = nxt;
}
}
}
void init(){
a.clear();
for (int i = 1; i <= prevn; i++)
e[i].clear();
prevn = n;
}
void sort_a(){
static int buc[N];
static int last_max = 0;
for (int i = 0; i <= last_max; i++)
buc[i] = 0;
int mx = 0;
for (auto v : a){
buc[v]++;
if (v > mx)
mx = v;
}
last_max = mx;
a.clear();
for (int i = mx; i >= 1; i--){
for (int j = 1; j <= buc[i]; j++){
a.push_back(i);
}
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> T;
for (int cs = 1; cs <= T; cs++){
cin >> n >> m;
init();
for (int i = 1, u, v; i < n; i++){
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
int rt = dfs1(1);
dfs1(rt);
dfs2(rt);
sort_a();
int ans = 1;
for (auto v : a){
ans++;
if (m > v)
m -= v;
else
break;
}
cout << ans << "\n";
}
}
不是哥们,卡我常,还卡我空间?
LGT618706 「ZYZ 2025」地铁
原题链接:「ZYZ 2025」地铁
分析
我们只需要枚举最后是哪辆车到达终点即可。
正解
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 200005;
int n, S, E, v0;
struct node{
int t, v;
}a[N];
bool cmp(node o, node p){
return o.t < p.t;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> S >> E >> v0;
int k = 0;
for (int i = 1, x, y; i <= n; i++){
cin >> x >> y;
if (y >= v0){
a[++k].t = x;
a[k].v = y;
}
}
sort(a + 1, a + k + 1, cmp);
int d = E - S;
double ans = (double)d / v0;
for (int i = 1; i <= k; i++){
double tim = (double)a[i].t + (double)E / a[i].v;
ans = min(ans, tim);
}
cout << fixed << setprecision(10) << ans << '\n';
return 0;
}