二分答案杂题选做
无特殊注明,所有数据范围都有下界 (或 ) .
代码要不放个 submission id?我看不行。
1. 聪明的质监员
题面
有俩长度为 的序列 和 个区间 ,给一个整数 .
找到一个 使得
最小 . 求这个最小值
,,.
题解
二分答案(因为 太大时上面的 ,所以右界可以设 ),结果变成给一个 ,求那个巨大长柿子 .
发现当 一定的时候,第一个 求和出来的每项中, 和 都是永恒不变的,只有区间 变了
于是前缀和,单次查询就是 .
于是总复杂度为 .
代码
#define int long long
using namespace std;
typedef long long ll;
const int N = 200500;
int n, m, s;
int w[N], v[N], L[N], R[N];
ll psumA[N], psumB[N];
ll calc(int W) // calc s
{
for (int i=1; i<=n; i++)
{
psumA[i] = psumA[i-1] + (w[i] > W);
psumB[i] = psumB[i-1] + (w[i] > W) * v[i];
}
ll ans = 0;
for (int i=1; i<=m; i++)
ans += (psumA[R[i]] - psumA[L[i]-1]) * (psumB[R[i]] - psumB[L[i]-1]);
return ans;
}
signed main()
{
scanf("%lld%lld%lld", &n, &m, &s);
int l = 0, r = 0;
for (int i=1; i<=n; i++) scanf("%lld%lld", w+i, v+i), r = max(r, w[i]);
for (int i=1; i<=m; i++) scanf("%lld%lld", L+i, R+i);
ll ans = 0x3f3f3f3f3f3f3f3f; r += 10;
while (l <= r)
{
int mid = (l + r) >> 1;
ll x = calc(mid);
if (x > s) l = mid + 1;
else r = mid - 1;
ans = min(ans, abs(x - s));
} printf("%lld\n", ans);
return 0;
}
2. Monthly Expense 月度开支
题面
给一个长度为 的序列 ,把它分成连续的 段,要求每段数之和的最大值尽可能小,输出这个值 .
题解
二分答案,贪心判定 .
代码
typedef long long ll;
const int N = 200500;
int n, m, a[N];
bool check(int t)
{
int cnt = 0, sum = 0;
for (int i=1; i<=n; i++)
{
if (a[i] > t) return false;
int already = sum + a[i];
if (already > t){++cnt; sum = a[i];}
else sum = already;
} return cnt + 1 <= m;
}
int main()
{
scanf("%d%d", &n, &m);
int l = 0, r = 0; // max r = 1e9 < INT_MAX
for (int i=1; i<=n; i++) scanf("%d", a+i), r += a[i];
int ans = 0x3f3f3f3f;
while (l <= r)
{
int mid = (l + r) >> 1;
if (check(mid)){r = mid - 1; ans = min(ans, mid);}
else l = mid + 1;
}
printf("%d\n", ans);
return 0;
}
3. 栅栏
题面
有俩长度分别为 的序列 ,我们有操作
指定一个 ,先令 ,并令 ,,其中 是任意整数
要求在每个时刻都不能有元素 .
先进行若干次操作,问有多少对 使得 ,注意每个 和每个 都互不相同 .
,,序列的元素不超过 .
题解
二分答案,于是问题变为判定取 的前 个元素组成序列 是否可行.
这可以 dfs 判断,但是会 TLE,优化方法有二:
- 随机化
- 剪枝
我们自然是不喜欢剪枝的,于是随机化哈哈 .
具体说,就是随机那个 序列然后上贪心,跑个 3000 次就行了
这种魔法一般被称作:随机化贪心 .
剪枝做法比较高妙,可以看洛谷 P1528 / P2329 题解(dX!dX!dX!).
代码(随机)
有个魔法:https://www.cnblogs.com/CDOI-24374/articles/15612874.html
但是没用。
暴力:
using namespace std;
mt19937 rnd(random_device{}());
typedef long long ll;
const int M = 51, N = 2222;
int n, m, a[M], b[N], c[N], sum, now = 0;
bool check(int t)
{
for (int i=1; i<3333; i++)
{
memcpy(c, a, sizeof a);
shuffle(c+1, c+1+m, rnd); // C++11
bool vis = true;
for (int i = t; i >= 1; i--)
{
bool now = false;
for(int j = 1; j <= n; j++)
if (c[j] >= b[i]){c[j] -= b[i]; now = true; break;}
if (!now){vis = false; break;}
}
if (vis) return true;
} return false;
}
int main()
{
scanf("%d", &m);
for (int i=1; i<=m; i++) scanf("%d", a + i), sum += a[i];
scanf("%d", &n);
for (int i=1; i<=n; i++) scanf("%d", b + i);
sort(b + 1, b + 1 + n);
int l = 0, r = n, ans = 0;
while (l <= r)
{
int mid = (l + r) >> 1;
if (check(mid)){l = mid + 1; ans = max(ans, mid);}
else r = mid - 1;
} printf("%d\n", ans);
return 0;
}
代码(贪心)
using namespace std;
typedef long long ll;
const int M = 51, N = 2222;
int n, m, a[M], b[N], c[N], ps[N], sum, now = 0;
bool dfs(int d, int lst) // d 目前数量
{
if (d<=0) return true;
if (now + ps[d] > sum) return false; //
for (int i = lst; i<=m; i++)
{
if (c[i] >= b[d])
{
c[i] -= b[d];
if (a[i] < b[1]) now += c[i];
if (dfs(d-1, (b[d] == b[d-1]) ? i : 1)) return true;
if (c[i] < b[1]) now -= c[i];
c[i] += b[d];
}
} return false;
}
bool check(int t){memcpy(c, a, sizeof a); now = 0; return dfs(t, 1);}
int main()
{
scanf("%d", &m);
for (int i=1; i<=m; i++) scanf("%d", a + i), sum += a[i];
scanf("%d", &n);
for (int i=1; i<=n; i++) scanf("%d", b + i);
sort(a+1, a+1+m); sort(b+1, b+1+n);
for (int i=1; i<=n; i++) ps[i] = ps[i-1] + b[i];
while (ps[n] > sum) --n;
int l = 0, r = n, ans = 0;
while (l <= r)
{
int mid = (l + r) >> 1;
if (check(mid)){l = mid + 1; ans = max(ans, mid);}
else r = mid - 1;
} printf("%d\n", ans);
return 0;
}
4. 覆盖问题
题面
给若干个点,用三个 的正方形覆盖它们,求 的最小值 .
点数量不超过 .
题解
二分答案,就变成给一个 ,问三个 正方形能不能覆盖了 .
首先肯定有两个放在角上,枚举咋放,问题就变成给若干点,问一个 正方形能不能覆盖力 .
这个有手就行,于是问题解决 .
代码
因为我写的不知道哪里错了,用数据点分治过的,所以不放了 .
5. 木棍分割
题面
有 根木棍,第 根木棍的长度为 , 根木棍依次连结了一起,总共有 个连接处. 现在允许你最多砍断 个连接处, 砍完后 根木棍被分成了很多段,要求满足总长度最大的一段长度最小,并且输出有多少种砍的方法使得总长度最大的一段长度最小,并将结果对 取模 .
, .
题解
二分部分和 一样,方案数就简单 dp 一下 .
即设 为到第 根, 分 处,转移随便转 .
可以前缀和优化,转移点也可以预处理,甚至还可以滚数组哈哈 .
代码和题解有出入 .
代码
using namespace std;
typedef long long ll;
const int N = 55555, M = 1111, P = 10007, I = 0x3f3f3f3f;
int n, m, a[N], rt[N], S[N], dp[N];
void updateS(){for (int i=1; i<=n; i++) S[i] = S[i-1] + dp[i];} // 更新前缀和
bool check(int k)
{
int cnt = 0, sum = 0;
for (int i=1; i<=n; i++)
{
if (sum + a[i] > k){++cnt; sum = a[i];}
else sum += a[i];
if (cnt > m) return false;
} return cnt <= m;
}
int main()
{
scanf("%d%d", &n, &m);
int l = 0, r = 0, ans = I;
for (int i=1; i<=n; i++) scanf("%d", a+i), l = max(l, a[i]), S[i] = S[i-1] + a[i];
r = S[n];
while (l < r)
{
int mid = (l + r) >> 1;
if (check(mid))r = ans = mid;
else l = mid + 1;
}
printf("%d ", ans);
int now = 0;
for (int i=1; i<=n; i++)
for (; now<i; now++)
if (S[i] - S[now] <= ans){rt[i] = now; break;}
for (int i=1; i<=n; i++) dp[i] = (S[i] <= ans);
int A = (S[n] <= ans);
updateS();
for (int i=2; i<=m+1; i++)
{
for (int j=1; j<=n; j++)
{
dp[j] = S[j-1];
if (rt[j]-1 >= 0)
dp[j] = (dp[j] - S[rt[j]-1] + P ) % P;
} updateS(); A = (A + dp[n]) % P;
}
printf("%d\n", A);
return 0;
}
6. 猜数游戏
题面
有一个任意两数不同且长度为 的序列 ,对它有 个信息,形如 的最小值是 .
问在第几个信息时产生矛盾?
, .
题解
二分答案,然后用这个:[link](https://www.cnblogs.com/CDOI-24374/p/15624655.html
代码
// https://darkbzoj.tk/submission/165217
#include <iostream>
#include <cmath>
#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <cstring>
#include <bits/c++0x_warning.h> // *
using namespace std;
const int N = 1e6+500, Q = 25555, I = 0x3f3f3f3f;
inline void chkmin(int& a, int b){if (a > b) a = b;}
inline void chkmax(int& a, int b){if (a < b) a = b;}
struct Input
{
int l, r, v;
bool operator < (const Input& x)const{return v > x.v;}
}inp[Q], t[Q];
int n, q;
struct Magic
{
int fa[N]; // dsu
void init(){for (int i=0; i<=n+3; i++) fa[i] = i;}
void clear(){init();}
int get(int x){return fa[x] == x ? x : fa[x] = get(fa[x]);}
void merge(int l, int r)
{
for (int u = l; u <= r; u++)
fa[get(u)] = get(r+1); // dsu union
}
bool crs(int l, int r){return get(l) > r;}
Magic(){init();}
}T;
bool check(int r) // -> is NOT true
{
T.clear();
for (int i=1; i<=r; i++) t[i] = inp[i];
sort(t+1, t+1+r);
int lmin, lmax, rmin, rmax;
lmin = lmax = t[1].l; rmin = rmax = t[1].r;
for (int i=2; i<=r; i++)
{
if (t[i].v == t[i-1].v) // Case 1
{
lmin = min(lmin, t[i].l); lmax = max(lmax, t[i].l);
rmin = min(rmin, t[i].r); rmax = max(rmax, t[i].r);
if (rmin < lmax) return true;
continue;
} // Case 2
if (T.crs(lmax, rmin)) return true;
T.merge(lmin, rmax);
lmin = lmax = t[i].l; rmin = rmax = t[i].r;
}
return T.crs(lmax, rmin);
}
int main()
{
scanf("%d%d", &n, &q); T.init();
for (int i=1, l, r, a; i <= q; i++) scanf("%d%d%d", &inp[i].l, &inp[i].r, &inp[i].v);
int l = 1, r = q, ans = I;
while (l <= r)
{
int mid = (l + r) >> 1;
if (check(mid)){r = mid - 1; ans = min(ans, mid);}
else l = mid + 1;
}
if (ans == I) puts("0");
else printf("%d\n", ans);
return 0;
}
7. Exercise 奶牛健美操
题面
给一棵 个点的树,割 条边,让直径最小,输出最小直径 .
.
题解
二分答案,就变成要直径 ,那么至少要割多少条边 .
我们知道直径就是最长链和次长链拼起来,于是可以 dp .
代码
using namespace std;
const int N = 222222;
vector<int> g[N];
int n, s, dp[N], t[N], ans = 0;
void addedge(int u, int v){g[u].push_back(v);}
void ade(int u, int v){addedge(u, v); addedge(v, u);}
// 直径:最大链和次大链拼起来
void dfs(int u, int fa, int p)
{
int l = g[u].size(), cc = 0;
for (int i=0; i<l; i++)
if (g[u][i] != fa) dfs(g[u][i], u, p);
for (int i=0; i<l; i++)
if (g[u][i] != fa) t[++cc] = dp[g[u][i]] + 1;
sort(t + 1, t + 1 + cc);
int ptr = cc;
while (ptr && (t[ptr] + t[ptr - 1] > p)){--ptr; ++ans;}
dp[u] = t[ptr];
}
bool check(int p)
{
ans = 0; dfs(1, 0, p);
return ans <= s;
}
int main()
{
scanf("%d%d", &n, &s);
for (int i=1, u, v; i<n; i++) scanf("%d%d", &u, &v), ade(u, v);
int l = 1, r = n, ans = 0x3f3f3f3f;
while (l <= r)
{
int mid = (l + r) >> 1;
if (check(mid)){r = mid - 1; ans = min(ans, mid);}
else l = mid + 1;
} printf("%d\n", ans);
return 0;
}
以下是博客签名,正文无关
本文来自博客园,作者:yspm,转载请注明原文链接:https://www.cnblogs.com/CDOI-24374/p/15612351.html
版权声明:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0)进行许可。看完如果觉得有用请点个赞吧 QwQ
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】