二分答案杂题选做

无特殊注明,所有数据范围都有下界 0(或 1) .

代码要不放个 submission id?我看不行。

1. 聪明的质监员

题面

有俩长度为 n 的序列 wi,vim 个区间 [li,ri],给一个整数 S .

找到一个 k 使得

|Si=1m((j=liri[wjk])(j=liri[wjk]vj))|

最小 . 求这个最小值


n,m200000wi,vi106s1012.

题解

二分答案(因为 k 太大时上面的 [wjk]=0,所以右界可以设 maxwi),结果变成给一个 k,求那个巨大长柿子 .

发现当 k 一定的时候,第一个 求和出来的每项中,[wjk]vj 都是永恒不变的,只有区间 [li,ri] 变了

于是前缀和,单次查询就是 O(n+m) .

于是总复杂度为 O((n+m)log(maxwi)) .

代码

#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 月度开支

题面

给一个长度为 n 的序列 a,把它分成连续的 m 段,要求每段数之和的最大值尽可能小,输出这个值 .


n105

题解

二分答案,贪心判定 .

代码

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. 栅栏

题面

有俩长度分别为 m,n 的序列 a,b,我们有操作

指定一个 1km,先令 mm+1,并令 amrak=akr,其中 r 是任意整数

要求在每个时刻都不能有元素 0 .

先进行若干次操作,问有多少对 (i,j) 使得 ai=bj,注意每个 i 和每个 j 都互不相同 .


m50n1000,序列的元素不超过 32767 .

题解

二分答案,于是问题变为判定取 a 的前 r 个元素组成序列 a 是否可行.

这可以 dfs 判断,但是会 TLE,优化方法有二:

  • 随机化
  • 剪枝

我们自然是不喜欢剪枝的,于是随机化哈哈 .

具体说,就是随机那个 a 序列然后上贪心,跑个 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. 覆盖问题

题面

给若干个点,用三个 L×L 的正方形覆盖它们,求 L 的最小值 .


点数量不超过 20000 .

题解

二分答案,就变成给一个 L,问三个 L×L 正方形能不能覆盖了 .

首先肯定有两个放在角上,枚举咋放,问题就变成给若干点,问一个 L×L 正方形能不能覆盖力 .

这个有手就行,于是问题解决 .

代码

因为我写的不知道哪里错了,用数据点分治过的,所以不放了 .

5. 木棍分割

题面

n 根木棍,第 i 根木棍的长度为 lin 根木棍依次连结了一起,总共有 n1 个连接处. 现在允许你最多砍断 m 个连接处, 砍完后 n 根木棍被分成了很多段,要求满足总长度最大的一段长度最小,并且输出有多少种砍的方法使得总长度最大的一段长度最小,并将结果对 10007 取模 .


n50000mmin{n1,1000} .

题解

二分部分和 2 一样,方案数就简单 dp 一下 .

即设 dpi,j 为到第 i 根, 分 j 处,转移随便转 .

可以前缀和优化,转移点也可以预处理,甚至还可以滚数组哈哈 .

代码和题解有出入 .

代码

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. 猜数游戏

题面

有一个任意两数不同且长度为 n 的序列 a,对它有 q 个信息,形如 al,al+1,,ar 的最小值是 r .

问在第几个信息时产生矛盾?


n106q25000 .

题解

二分答案,然后用这个:[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 奶牛健美操

题面

给一棵 n 个点的树,割 m 条边,让直径最小,输出最小直径 .


1m<n105 .

题解

二分答案,就变成要直径 M,那么至少要割多少条边 .

我们知道直径就是最长链和次长链拼起来,于是可以 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;
}

posted @   yspm  阅读(44)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 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】
😅​
点击右上角即可分享
微信分享提示