Loading

kauaiの水题记录——二分

1. 中位数

第一眼看这道题,想到可以先开一个数组,每加入一个数就用\(sort\)​去排序,然后取中间的数,复杂度是\(O(n^2logn)\),所以可以使用\(vertor\),因为\(vertor\)​​支持在数组中随意插入一个数,所以我们只要找到这个数在哪里就行了,进而想到可以二分,然后就可以了。


这里介绍一下\(vector\)

#include <vector>//头文件
vector<int> s;
s.push_back(a);//尾部插入
s.pop_back();//尾部删除
vertor<int>::iterator it;//声明迭代器
int k=*s.begin();//s.bejin()会返回第一个元素的迭代器,可以用*解除引用
s.insert(s.begin()+i,a)//在第i+1个元素中插入a
s.erase(s.begin()+1)//删除第二个元素
s.erase(s.begin()+i,s.end()+j)//删除区间[i,j-1],区间从零开始
int y=*upper_bound(a.begin(),a.end(),x)//查找第一个大于x的数

2. 山

题目中说要我们找最小的y坐标,想到可以二分答案,接着想如何判断二分出来的情况是否合法,把每两个相邻折点连成的线段想象成一个一次函数\(y=kx+b\)​,如果灯的位置在每条直线的上方就是合法的,作一条\(y=mid\)​的直线,和每条一次函数就会有交点,限制灯的横坐标的范围\(x1,x2\)​然后进行分类讨论,如果\(k>0\)​,显然横坐标要在交点的左边,反之同理,要注意的是我们还要判断\(k=0\)​的清况,此时要注意的就是纵坐标的,因为灯一定要比这条直线高,最后判断范围是否合法即可。我们可以预处理出每条直线的\(k,b\)以此来减小常数,要注意实数域上的二分。

    while(l+eps<r){
	double mid=(l+r)/2.0;
	if(check(mid)){
	    r=mid;
	    ans=mid;
	}
	else l=mid;
    }

一般eps要比题目要求的精度多两位。

3. 弹幕考察

这道题如果理解了题意还是比较简单的,可以看作是一个区间覆盖问题,首先我们可以打一个\(O(nm)\)的暴力,对于每一次询问都去枚举每一区间,看是否满足要求,然后累加答案,这样可以拿30分。

其实我们可以把每一个区间都看作两个端点,并开一个\(l[],r[]\)​数组记录位置,询问的时候二分查找\(l[]\)​中第一个大于等于查询区间右端点的数的位置,\(r[]\)​中第一个大于等于查询区间左端点数的位置,然后相减就行了。

4. 赛道修建

这道题我也不是太懂,就随便口胡一下,想到二分答案,二分最小的赛道长度,对于每一个结点,我们可以存一个\(val\),从下往上传,然后放进\(multiset\)中,分两种情况讨论,如果\(val>=k\)​说明这个点和它的子树已经可以满足条件了,答案+1,否则就把当前这条边放进\(multiset\)中,接着看第二种情况,以这个点为根的子树的两条边相加会大于等于\(k\)​​,贪心地想,两个数是不是越小越好,因为我们要把大的数上传到上面去,所以我们可以在\(multiset\)里二分去找这个数,然后把这两个点删除,\(ans\)\(++\),找不到就和要上传的\(max\)进行比较,要说明一下,因为一个子树肯定只有一条边连接着上面的结点,所以要去找\(max\)​。最后判断\(ans\)是不是大于\(m\)就可以知道是不是合法的了。

核心代码:


int dfs(int x,int fa,int k){
    s[x].clear();
    int val;
    for(int i=head[x];i;i=nxt[i]){
	int y=ver[i];
	if(y==fa) continue;
	val=dfs(y,x,k)+edge[i];
	if(val>=k) tot2++;
	else{
	    s[x].insert(val);
	}
    }
    int Max=0;
    while(!s[x].empty()){
	if(s[x].size()==1) return max(Max,*s[x].begin());
	it=s[x].lower_bound(k-*s[x].begin());
	if(it==s[x].begin()&&s[x].count(*it)==1) it++;
	if(it==s[x].end()){
	    Max=max(Max,*s[x].begin());
	    s[x].erase(s[x].find(*s[x].begin()));
	}
	else{
	    tot2++;
	    s[x].erase(s[x].find(*it));
	    s[x].erase(s[x].find(*s[x].begin()));
	}
    }
    return Max;

5. [SHOI2015]自动刷题机

说实话,这道题真的比较水,感觉不值得评蓝,题目要我们求最大和最小值,所以想到二分,我们可以二分两次,然后对着题意模拟判断是否合法就行了。

比较难的就是如何二分最大和最小值了,其实就是把\(ans\)​放到\(l\)\(r\)区间里了。

    while(l<=r){
	int mid=(l+r)>>1;
	int Quick_kk=check(mid);
	if(Quick_kk<=k){//求最小值
	    r=mid-1;
	    if(Quick_kk==k){ans1=mid;}
	}
	else l=mid+1;
    }
    l=1,r=1e18;
    while(l<=r){//求最大值
	int mid=(l+r)>>1;
	int Quick_kk=check(mid);
	if(Quick_kk>=k){
	    l=mid+1;
	    if(Quick_kk==k){ans2=mid;}
	}
	else r=mid-1;
    }

6. [JXOI2017]加法

首先二分答案,操作后的最小值,接着贪心地想,除非一定要加,否则就不加,我们可以开一个优先队列,里面存放每个区间的右端点,列举到当前的数时,就在右端点最大的区间里加,因为左边的数已经加完了,用差分维护区间加法,最后判断操作次数是否小于等于 \(k\)​,和操作完后是否大于等于最小值。

7. [POI2013]LUK-Triumphal arch

首先二分最小的 \(k\)​​​​​​,然后统计每个结点下一级儿子的个数存在\(sum[]\)​​​​中,用 \(f[x]\)​​​​​ 数组统计以x为根的字数内需要支援的次数,初始化为 \(sum[x]-k\)​​​​,如果\(f\)​​​​​​数组大于 \(0\)​​​ 则向上传,如果小于零也没什么用,因为是从上往下染色的,最后判断\(f[1]\)​​​​是否小于等于 \(0\)​​​ 。

8. [HEOI2016/TJOI2016]排序

\(sort\)​ 可以拿 \(80\)​​ 分!

二分第 \(p\) 位上的数到底是什么,考虑 \(check()\) ,把序列里大于等于 \(mid\) 的数改为 \(1\) ,其余为 \(0\),这样排序时只需要改变 \(1,0\) 的位置即可,显然可以用线段树维护,最后判断第 \(p\) 位上的数是否为 \(1\) ,是则为合法的答案,因为为 \(1\) 则说明\(a[p]>=mid\),二分求出 \(mid\) 的最大值也就是等于 \(a[p]\) 的情况。

posted @ 2021-08-04 20:50  Miraii  阅读(53)  评论(2)    收藏  举报