【算法】【二分】

21 阅读4分钟

Test Generator

题意简述
给定两个整数 ( s ) 和 ( m ),需要构造一个非负整数数组 ( a ),满足:

  1. 数组元素之和等于 ( s );
  2. 每个元素 ( a_i ) 的二进制位中,为 1 的位必须是 ( m ) 的二进制位中也为 1 的位(即 ( a_i & m = a_i ))。

要求判断是否存在这样的数组。若存在,输出数组的最小可能长度 ( n );否则输出 (-1)。

#include <bits/stdc++.h>  
using namespace std;  
using ll=long long;  
void so(){  
    ll s,m;cin>>s>>m;  
    ll ans=0;  
    int ok=0;  
    ll k=s;  
    for(int i=0;i<=62;i++){  
        if((m>>i)&1){  
            if(k%(1ll<<i)==0)ok=1;  
            break;  
        }//至少可以由最第低位构成  
    }if(ok==0){cout<<-1<<'\n';return;}  
    ll l=1,r=1e18;  
    while(l<=r){  
        ll mid=(l+r)/2;  
        s=k;  
        for(int i=62;i>=0;i--){  
            if((m>>i)&1){  
                ll d=min(mid,s/(1ll<<i));//优先用大的  
                //不可以超过mid次  
                s-=d*(1ll<<i);  
            }  
        }if(s>0){  
            l=mid+1;//不够再加  
        }else r=mid-1;  
        //最后l>r;  
        //当<=0,r去试更小的  
    }cout<<l<<'\n';  
}int main(){  
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);  
    int t;cin>>t;  
    while(t--){so();}  
}

注意与启示

1.求最短的,可以想到二分

2.先不要看s,m有1的话s马上减,最多只能减mid(大的数先能用几次先用)


Turtle vs. Rabbit Race: Optimal Trainings

#include<bits/stdc++.h>
using namespace std;
#define int long long 
signed main(){
	int t=1;cin>>t;
	while(t--){
	
	int n;cin>>n;
	vector<int>a(n+1);
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}int q;cin>>q;
	vector<int>ans;
	int ps[n+1];
	ps[0]=0;
	for(int i=1;i<=n;i++){ps[i]=ps[i-1]+a[i];	
	}
	while(q--){
		int l,u;cin>>l>>u;
		int lb=l,rb=n;
		//二分找总段数<=u的最大r 
		while(lb<rb){
			int mid=(lb+rb+1)/2;
			if(ps[mid]-ps[l-1]<=u){lb=mid;
			}else rb=mid-1;
		}
	int mu=-1e18;
	int op;
	for(int i=max(l,lb-2);i<=min(n,lb+2);i++){
		int t=ps[i]-ps[l-1];
		int ut=(u+(u-t+1))*t/2;
		if(ut>mu){mu=ut;op=i;
		
		}
	}ans.push_back(op);
	}for(int i=0;i<ans.size();i++)cout<<ans[i]<<' ';
	cout<<'\n';
}
	return 0;
}

代码解析和注意事项

1.当二分求最大值时,要向上取整:向下可能会导致有些值无法取到

2.有想到前缀和,但是没想到二分(或许upper_bound就是二分)

3.我想到了二分,但是没想到误差怎么处理,但是想想其实就是在lb+1和lb上选,因为lb+1中已经有负的了,所以直接二分再在附近找

atcoder.jp/contests/ab…

#include <bits/stdc++.h>  
using namespace std;  
#define int long long  
  
#define mid ((l+r)>>1)  
using ll =__int128 ;  
const int N=3e5+10;  
struct Node{  
    int d,t;  
    friend bool operator<(Node A,Node B){  
        return A.d!=B.d?A.d>B.d:A.t>B.t;  
    }  
}f[N];  
int tot,n,H;  
int use[N],Ti[N];  
//从t1到t2,一直用now,造成的伤害  
ll cou(int t1,int t2,Node& now){  
    ll sum=0;  
    //超过了持续的时间  
    if(t2>=now.t){  
        sum+=(ll)now.d*now.t*(t2-max(now.t,t1)+1);  
        t2=max(now.t,t1)-1;  
    }if(t2>=t1){  
        sum+=(ll)now.d*(t2+t1)*(t2-t1+1)/2;  
    }return sum;  
}  
bool che(int t){  
    ll s=0;  
    //所有最优法术分段计算  
    for(int i=tot;i>=1;i--){  
        //过t,不用算  
        if(Ti[i-1]>=t)continue;  
        Node &no=f[use[i]];  
        s+=cou(Ti[i-1]+1,t,no);  
        //上段结束于time[i-1]  
        t=Ti[i-1];  
    }return s>=H;//可以杀  
}  
void so(){  
      
}signed main(){  
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);  
    cin>>n>>H;  
    for(int i=1;i<=n;i++)cin>>f[i].t>>f[i].d;  
    //优先用伤害最高的  
    sort(f+1,f+1+n);  
    //选最d*t严格递增的,差的丢  
    tot=0;  
    //use[0]=1;  
    for(int i=1;i<=n;i++){  
        if(f[i].d*f[i].t>f[use[tot]].d*f[use[tot]].t)use[++tot]=i;  
    }//算出切换分界点  
    //什么时候,从i个换i+1  
    for(int i=1;i<tot;i++){  
        Node& now=f[use[i]],nxt=f[use[i+1]];  
        Ti[i]=((__int128)now.d*now.t)/nxt.d;  
    }//倒数第 x 回合,放 now 更好 vs 放 nxt 更好  
    //什么时候两个技能伤害一样?  
    //now 技能伤害 = nxt 技能伤害  
    //d_now × x = d_nxt × t_nxt  
    int l=0,r=1e18+1;  
    while(l+1<r){  
        if(che(mid))r=mid;  
        else l=mid;  
    }cout<<r;  
}

思路

从后往前遍历,第倒i秒的范围为(i,t)

所以倒1秒用最大的

后来是否要换取决于t[i]* d[i]是不是大于t[i+1]* d[i+1]