7.12 Test1考试爆炸记

下面提供链接:

链接: 考试试题及程序

密码:要密码的请q我,本人qq,暂不透露。

这次考试最大的失败其实并不是我打的所有题都是用暴力打的,而是所有题都是二分的题目但是我并没有看出来,我就只看出来一道题是二分的题,就是第二题,因为实在是太过于明显了,第一道题我以为是一道dp然后弄了半天一点思路都没有,然后就只好打暴力,第二题我看出来是二分了,但是可惜我的代码实现能力还是太菜了,所以也没有打出来,第三道题我以为是随机化(鬼知道我当时是怎么想的!)然后我就用了一些妙妙的方法优化了一下我的暴力,然后还是只得了暴力20分,是不是菜的一哔!下面我就不谈我的考试心得了,我们来分别分析一下这三道题应该怎么做!

T1

一道很明显的二分题,也需要一些数学知识,我们慢慢来讲,因为本人太菜,用不来markdown的公式编辑器,所以下面一段只有手写了!无奈。

我知道这个图片大家看不清楚,上面那个是预览图,下面是下载链接:

T1解题思路

更正:解题思路中排序应该是从小到大排序,不应该是从大到小排序,在代码中注释会给出原因及解释。

代码如下:

#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
struct sd{
	int v,r;
	double vv,rr,su;
	double rv;
}a[100005];
int n,k;
double rrv[100005]; 
bool cmp(sd aa,sd bb)
{
	if(aa.su<bb.su) return true;
	return false;
}
bool check(double mid)
{
	for(int i=1;i<=n;++i)
	{
		a[i].vv=a[i].v*mid;
		rrv[i]=a[i].vv-a[i].r;
		a[i].su=a[i].vv-a[i].r;
	}
	sort(rrv+1,rrv+1+n);//只有从小到大排序才能够确定我们每一次选进去的东西单位人品值最大,这样保证解最优。 
	double suu=0;
	for(int i=1;i<=k;++i)
	{
		suu+=rrv[i];
	}
	if(suu<=0) return true;
	return false;
}
int main()
{
	scanf("%d%d",&n,&k);
	double maxx=0;
	for(int i=1;i<=n;++i)
	{
		scanf("%d%d",&a[i].v,&a[i].r);
		maxx=max(maxx,(double)a[i].r/(double)a[i].v);
	}
	double p1=0,p2=maxx;
	double ans;
	while(p2-p1>0.001)
	{
		double mid=(p2+p1)/2;
		if(check(mid)){ans=mid;p1=mid;}
		else{p2=mid;}
	}
	printf("%.2lf",ans);
	return 0;
}

T2

T2这个题二分大家应该很明显就可以看出来吧。复杂的部分其实是树上的贪心,这部分需要一点点小技巧,所以说等会儿说到这里了我们在继续讲,首先我们还是按照常规套路进行二分答案,然后我们还是把答案带入树中进行树上对可行性进行验证,至于怎么验证呢主要是贪心,怎么贪心呢?,这个需要一点技巧,我们要保证解最优,所以我们先把树上每个点的子树的大小先预处理出来,然后我们从叶节点往根节点进行搜索,当我们发现我们有一个叶节点的数刚好大于我们二分的答案,我们就把这个子树锯掉,依次类推我们锯完以后我们看一下有多少组,如果组数太少说明我们二分的答案太大了,我们把二分区间左移,如果组数刚或多了,说明我们二分答案有可能小了,我们把答案右移,如果发现刚好分够m组但是有一个答案比二分答案小,说明分不够那么多组,我们就需要按照分组过少来处理这个问题,即二分答案大了,二分区间左移。为什么这样分是最优的呢?我们想一下,如果我们一直分下去,那么肯定分的组会越来越少,达不到分m组的目的,并且我们不这样分(不及时锯断子树)那么我们剩下的分为一组剩余能力之和比二分答案小的可能性越大,不满足我们想达到的目的,所以说我们要及时锯断子树来达到贪心的目的。

这个代码是大佬给我的我也在学习中,分享一下吧!

#include<bits/stdc++.h>
using namespace std;
struct front_star{
    int to,next;
}edge[10005<<1];
struct poi{
    int num,dp;
}deep[10005];
int n,m,cnt=0,ans;
int sz[10005],fa[10005],val[10005],head[10005];
bool cmp(poi a,poi b)
{
    return a.dp>b.dp;
}
void addedge(int u,int v)
{
    cnt++;
    edge[cnt].to=v;
    edge[cnt].next=head[u];
    head[u]=cnt;
}
int get_deep(int u,int f)
{
    sz[u]=val[u];
    deep[u].num=u;
    for(int i=head[u];~i;i=edge[i].next)
        {
            int v=edge[i].to;
            if(v!=f)
               {
                   deep[v].dp=deep[u].dp+1;
                   sz[u]+=get_deep(v,u);
                   /*
                   printf("%d %d\n",u,sz[u]);      
                   */                      
               }
        }
    return sz[u];           
}
bool judge(int p)
{
    int tot=0;
    int temp[10005],sum[10005];
    memset(sum,0,sizeof(sum));
    for(int i=1;i<=n;i++)
        temp[i]=sz[i];
    for(int i=1;i<=n;i++)
        {
            temp[deep[i].num]-=sum[deep[i].num];
            sum[fa[deep[i].num]]+=sum[deep[i].num];
            if(temp[deep[i].num]>=p)
               {
                   tot++;
                   sum[fa[deep[i].num]]+=temp[deep[i].num];
               }
        }
    if(tot>=m)
       return true;
    else
       return false;        
}
int main()
{
    memset(head,-1,sizeof(head));
    memset(sz,0,sizeof(sz));
    scanf("%d%d%d",&n,&m,&val[1]);
    fa[1]=0;
    for(int i=2;i<=n;i++)
        {
            scanf("%d%d",&fa[i],&val[i]);
            addedge(i,fa[i]);
            addedge(fa[i],i);
        }
    deep[1].dp=1;    
    get_deep(1,1);
    sort(deep+1,deep+1+n,cmp);
    int L=1,R=sz[1]/m+1;
    while(L<=R)
          {
              int mid=(L+R)/2;
              if(judge(mid))
                 {
                    ans=mid;
                    L=mid+1;
                 }
              else
                  R=mid-1;   
          }
    printf("%d",ans);     
    return 0;
}

T3

这道题也是一个非常神奇的二分题目,因为不需要什么像T1一样的推导,所以说我就直接打了。

这道题其实你不需要考虑的太多,只要看出这是道二分答案,那么一切都简单了,因为他问的是第k大个(先不用管这个),我们先把整数序列A,B都先分别存入数组a,b数组中,然后我们分别对a,b数组进行从小到大排序,然后我们找出最小的乘积和最大的乘积这个其实就是他的二分区间,然后开始二分答案,对于我们每一次二分的答案我们都去验证一下答案的可行性(这个东西在踩石头那道题中我已经讲过了7.11二分中,大家可以返回目录去看一下)
我们只需要a数组从左向右扫b数组从右往左扫:

当扫到一个a*b
小于我们二分出来的答案时我们停止扫描,把有多少个数比我们的答案大的记录到一个变量tot中,然后此时a头上的指针右移一位然后b的指针从原来停止的位置继续左移,直到发现又一个比我们二分的答案小的数,在把此时扫出的比我们二分出来的答案大的数的个数累加到变量tot中,就这样当我们把a数组扫完的时候,其实我们就可以知道有多少个数比我们二分的答案大,即二分的答案排在第几位,我们设他排在第kk位,如果kk>k,说明我们二分的答案比我们要的东西小了,二分区间选在右边,否则二分区间选在左边,是不是感觉其实挺简单的。所以说这么简单的一个题,我并没有看出来是一个二分的题,自己也觉得挺可惜的,下次应该就有经验了!

标程下载地址
标程!快戳我!!!

密码:我不告诉你!

下面有一个整合版的下载地址:

Test 1整合版

posted @ 2020-07-17 15:14  Mudrobot  阅读(109)  评论(0编辑  收藏  举报