wqs二分

感觉一般可能要严谨证明的话还是有点麻烦,不如直接打表,或者先老实WA一发 来的快

  • 一般题目会有选恰好k个/次这样的限制
  • 大致就是通过二分斜率,然后通过dp,或者贪心计算出最大/最小值,然后通过判断这个最大/最小值对应的选的个数来调整
  • 需要注意的是,我们计算的相当于是截距,还要+/-kl才是答案

例题

https://www.luogu.com.cn/problem/CF802N为例
给定两个长度为 \(n\) 的序列 \(a_1, a_2, \ldots, a_n\)\(b_1, b_2, \ldots b_n\)。要求选出 \(i_1, i_2, \ldots, i_k\)\(j_1, j_2, \ldots, j_k\),满足

  • \(1\leq i_1< i_2<\ldots< i_k\)\(1\leq j_1< j_2<\ldots< j_k\)

  • \(i_p\leq j_p\)\(1\leq p \leq k\))。

最小化 \(\sum_{p=1}^k a_{i_p}+b_{j_p}\) 的值。

  • 首先我们来感性理解一下
  • 假如我们不考虑k这个限制,因为都是正的,肯定是一个都不选
  • 但是如果我们给每个\(b_i\)都减上一个数x,就有可能出现选的情况,并且随着数x的增大,选的数肯定是越来越多
  • 因此我们可以二分这个x,计算出选了多少个数,从而对x进行调整

更具体的分析

#include<bits/stdc++.h>
#define fo(i,a,b) for (ll (i)=(a);(i)<=(b);(i)++)
#define fd(i,b,a) for (ll (i)=(b);(i)>=(a);(i)--)
#define lc (o<<1)
#define rc ((o<<1)|1)
#define mk(x,y) make_pair((x),(y))
#define eb emplace_back
#define A puts("Yes")
#define B puts("No")
#define fi first
#define se second
using namespace std;
typedef double db;
typedef long long ll;
typedef unsigned long long ull;
typedef long long i64;
const ll inf=1ll<<60;
const ll mo1=1e9+9;
const ll mo2=1e9+7;
const ll P=131;
const ll Q=13331;
const int N=1e6+5;
struct node{
	ll x,op;
};
bool operator < (const node &a,const node &b){
	if (a.x!=b.x) return a.x>b.x;
	return a.op>b.op;
}
priority_queue<node> q;
ll n,k,a[N],b[N],ans;
ll check(ll x){
	while (!q.empty()) q.pop();
	
	ll cnt=0;	
	ans=0;
	fo(i,1,n) {
		q.push((node){a[i],0});
		if (b[i]-x+q.top().x<=0) {
			ans+=b[i]-x+q.top().x;
			if (!q.top().op) cnt++;
			q.pop();
			q.push((node){-(b[i]-x), 1});
		}
	}
	
	return cnt;
}
int main(){
//	freopen("data.in","r",stdin);
	
	scanf("%lld %lld",&n,&k);
	fo(i,1,n) scanf("%lld",&a[i]);
	fo(i,1,n) scanf("%lld",&b[i]);
	
	ll l=0,r=1e12,mid;
	while (l<r) {
		mid=(l+r)>>1;
		if (check(mid)>=k) r=mid;
		else l=mid+1;
	}

	check(l);
	printf("%lld",ans+k*l);
	

	return 0;
}
 
 
  • 在反悔贪心的时候,注意到是可以取等,并且权值相同时,优先让a在前面,就是为了尽可能的多选数。
  • 也就是说我们得到的是跟这条直线相切的最右端点
  • 因此只有当\(k \le cnt\)时才可能合法
  • 当mid增大时,cnt会增大,因此我们要二分得到最小的mid使得\(k \le cnt\)

[国家集训队] Tree I

题目描述

给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有 \(need\) 条白色边的生成树。

题目保证有解。

  • 加入我们给每条白色边-x,那么选择x的边数量肯定不会变得更少。
  • 在下面的代码中,我们排序定义了尽可能多选白色边,因此只有当\(k\le cnt\)才是合法的,而当mid增大,cnt也增大,因此我们要的就是最小的mid,使得\(k \le cnt\)
#include<bits/stdc++.h>
#define fo(i,a,b) for (ll (i)=(a);(i)<=(b);(i)++)
#define fd(i,b,a) for (ll (i)=(b);(i)>=(a);(i)--)
#define lc (o<<1)
#define rc ((o<<1)|1)
#define mk(x,y) make_pair((x),(y))
#define eb emplace_back
#define A puts("Yes")
#define B puts("No")
#define fi first
#define se second
using namespace std;
typedef double db;
typedef long long ll;
typedef unsigned long long ull;
typedef long long i64;
const ll inf=1ll<<60;
const ll mo1=1e9+9;
const ll mo2=1e9+7;
const ll P=131;
const ll Q=13331;
const int N=1e6+5;
ll f[N],n,m,k,t1,t2,tot,ans;
struct node{
	ll x,y,z,c;
};
bool operator < (const node&a,const node &b) {
	return a.z<b.z;
}
node e1[N],e2[N],e[N];
void R(ll &x){
	ll t=0; char ch;
	for (ch=getchar();!('0'<=ch && ch<='9');ch=getchar());
	for (;('0'<=ch && ch<='9');ch=getchar()) t=t*10+ch-'0';
	x=t;
}
int find(int x){
	if (x==f[x]) return x;
	return f[x]=find(f[x]);
}
ll check(ll x){
	fo(i,1,t1) e1[i].z-=x;
	ans=0;
	ll i=1,j=1,cnt=0,f1,f2,tot=0;
	
	while (i<=t1 && j<=t2) {
		if (e1[i].z<=e2[j].z)  {
			e[++tot]=e1[i];
			i++;
		}
		else{ 
			e[++tot]=e2[j];
			j++;
		}
	}
	while (i<=t1) e[++tot]=e1[i],i++;
	while (j<=t2) e[++tot]=e2[j],j++;
	
	fo(i,1,n) f[i]=i;
	fo(i,1,m) {
		f1=find(e[i].x); f2=find(e[i].y);
		if (f1^f2) {
			f[f1]=f2;
			if (!e[i].c) cnt++;
			ans+=e[i].z;
		}
	}
	
	fo(i,1,t1) e1[i].z+=x;
	
	return cnt;
}
int main(){
//	freopen("data.in","r",stdin);
//	
	ll c,x,y,z;
	
	scanf("%lld %lld %lld",&n,&m,&k);
	fo(i,1,m) {
		scanf("%lld %lld %lld %lld",&x,&y,&z,&c);
		x++; y++;
		if (!c) e1[++t1]=(node){x,y,z,0};
		else e2[++t2]=(node){x,y,z,1};
	}
	
	sort(e1+1,e1+t1+1);
	sort(e2+1,e2+t2+1);

	
	ll l=-1,r=200,mid;
	while (l<r) {
		mid=(l+r)>>1;
		if (check(mid)>=k) r=mid;
		else l=mid+1;
	}
	
	check(l);
	printf("%lld\n",ans+k*l);
	

	return 0;
}
 
 

[ABC218H] Red and Blue Lamps

题面翻译

题意翻译

现在你有 \(N\) 盏灯排成一列,编号为 \(1\)\(N\)。对于任意一盏灯,你可以将其变成红色或蓝色。你想让其中的 \(R\) 盏灯变成红色,\(N-R\) 盏灯变成蓝色。

当第 \(i\)\(i+1\) 盏灯以不同的颜色发光时,你将获得 \(A_i\) 点报酬。

请你计算当有 \(R\) 盏灯为红色,\(N-R\) 盏灯为蓝色时,你所获得的报酬和的最大值。

  • 又是恰好r个这样的限制,假如我们让每个选红色的,都可以加上一个x,那么当x增大时,在最优选择方案下红色的数量只会增大
  • 代码中我选择的是尽可能多选红色,因此只有当\(k\le cnt\)才是合法的,而当mid增大,cnt也增大,因此我们要的就是最小的mid,使得\(k \le cnt\)
#include<bits/stdc++.h>
#define fo(i,a,b) for(ll (i)=(a);(i)<=(b);(i)++)
#define fd(i,a,b) for(ll (i)=(a);(i)>=(b);(i)--)
#define mk(x,y) make_pair((x),(y))
#define pi pair<ll,ll>
#define eb emplace_back 
#define fi first 
#define se second
using namespace std;
typedef long long ll;
const ll mo = 1e9 + 7;
const ll inf = 1ll << 60;
const int N = 2e5 + 5;
ll a[N], n, k, ans, f[N][2], g[N][2];
ll check(ll x) {
    ans = 0;

    f[1][0] = x; g[1][0] = 1;
    f[1][1] = 0; g[1][1] = 0;

    fo(i, 2, n) {
        if (f[i - 1][0] <= f[i - 1][1] + a[i]) {
            f[i][0] = f[i - 1][1] + a[i];
            if (f[i - 1][0] == f[i - 1][1] + a[i]) {
                g[i][0] = max(g[i - 1][0], g[i - 1][1]) + 1;
            }
            else g[i][0] = g[i - 1][1] + 1;
        }
        else {
            f[i][0] = f[i - 1][0];
            g[i][0] = g[i - 1][0] + 1;
        }

        if (f[i - 1][0] + a[i] <= f[i - 1][1]) {
            f[i][1] = f[i - 1][1];
            if (f[i - 1][0] + a[i] == f[i - 1][1]) {
                g[i][1] = max(g[i - 1][0], g[i - 1][1]);
            }
            else g[i][1] = g[i - 1][1];
        }
        else {
            f[i][1] = f[i - 1][0] + a[i];
            g[i][1] = g[i - 1][0];
        }
        f[i][0] += x;
    }

    ans = max(f[n][0], f[n][1]);


    if (f[n][0] < f[n][1]) return g[n][1];
    else return g[n][0];
}
int main() {

    // freopen("data.in", "r", stdin);
    // freopen("data.out", "w", stdout);

    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> n >> k;
    fo(i, 2, n) cin >> a[i];

    ll l = -1e12, r = 1e12, mid;
    while (l < r) {
        mid = (l + r) >> 1;
        if (check(mid) >= k) r = mid;
        else l = mid + 1;
    }

    check(l);

    cout << ans - k * l;

    return 0;
}
posted @ 2024-10-24 20:30  gan_coder  阅读(24)  评论(0)    收藏  举报