2017ICPCECIC北方邀请赛

D Birthday present(简单数学)

  • 题目大意

    给你n个数( n3105 )和k( k106 ),其中的每一个数 ai 可以变成区间[ aik,ai ]的任意一个数,问这n个数的最大公因数是多少。

  • 分析

    我们发现自上而下地直接由这n个数得到这个公因数是很困难的,更可行的方法是自下而上的方法:

    给定一个数x,判断x是否是这n个(可变)数的公因子

    判定是比较容易的,若满足 ai%xk ,则x是 ai 的因子

    接下来就是找出最大的这个x了

    有两条启发式信息可以利用:

    1.x不大于这n个数中最下的数

    2. xk+1

    这样就遍历一下区间[k+1,min( ai )]就行了

    数据太弱这样的方法都过了

    还有一个优化是

    如果x不是n个数的公因子,那么x的倍数也一定不是这n个数的公因子,可以像筛素数那样筛去。

  • 代码

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<map>
#include<algorithm>
#include<set>
#include<stack>
using namespace std;
const int INF=99999999;
const int MAXN=1000005;
int n,k;
int a[MAXN];
int maxm;
int minm;
void In()
{
      maxm=0;
      minm=INF;
      for(int i=1;i<=n;i++)
      {
          scanf("%d",&a[i]);
          maxm=max(a[i],maxm);
          minm=min(a[i],minm);
      }
}
int  Work()
{
    if(minm<=k)return minm;
    int ans;
    for(int x=minm;x>=k+1;x--)
    {
          int cnt=0;
          for(int i=1;i<=n;i++)
          {
               if(a[i]%x<=k)cnt++;
          }
          if(cnt==n)return x;
    }
}
int main()
{
    while (scanf("%d%d",&n,&k)!=EOF)
    {
         In();
         cout<<Work()<<endl;
    }
    return 0;
}

F Teacher’s Day(二分+贪心)

  • 代码
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<map>
#include<algorithm>
#include<set>
#include<stack>
using namespace std;
const int INF=99999999;
const int MAXN=100005;
int n,m,a;
int b[MAXN];
int p[MAXN];
void In()
{
      for(int i=1;i<=n;i++)scanf("%d",&b[i]);
      for(int i=1;i<=m;i++)scanf("%d",&p[i]);
}
bool Check(int x)
{
      long long int need=0;
      for(int i=1;i<=x;i++)
      {
            if(b[n-x+i]<p[i])need+=p[i]-b[n-x+i];
      }
      if(need<=a)return 1;
      else return 0;
}
void Erfen()
{
      int ans=0;
      int l=0;
      int r=min(n,m);
      while(r-l>1)
      {
            int m=(r+l)/2;
            if(Check(m))l=m;
            else r=m;
      }
     if(Check(l))ans=l;
     if(Check(r))ans=r;
      int need=0;
      for(int i=1;i<=ans;i++)
      {
          need+=p[i];
      }
      printf("%d %d\n",ans,max(need-a,0));
      return ;
}
int main()
{
    while (scanf("%d%d%d",&n,&m,&a)!=EOF)
    {
         In();
         sort(b+1,b+n+1);
         sort(p+1,p+m+1);
         Erfen();
    }
    return 0;
}
/*
2 2 1
5 5
7 6

2 2 1
3 3
7  6
*/

H题 MJF wants to work(贪心)

  • 题目大意

    有n个任务,每个任务有一个开始时间x,终止时间y,工资c.

    MJF要从中选出两个任务使得这两个任务之间时间不想交,并且工资之和等于m。

    问这两个任务的时间之和最少是多少。

  • 分析

    一开始的思路是按照终止时间从小到大对任务进行排序,逐个处理每个任务。例如对于当前任务i,在i之前的所有任务已经按照工资分好组,那么我们应该到m-c[i]的类别里面去寻找和任务i 配对的任务,之后将任务i放入它所属的类别中。由于任务i可能和m-c[i]类别中的任务时间上有冲突,所以这里需要用二分来寻找一个不发生冲突的m-c[i]中最靠后的任务用第k个任务表示,也就是说m-c[i]类中第1到k个任务都是可行的,再用一个数组来维护前每个类别中的前k个任务中时间的最小值。

    看了题解后发现其实这样是想复杂了,有更巧妙的处理方法。

    回顾刚才的思路,我们在逐个处理任务中某一个任务的时候我们是在寻找一个和这个任务时间不冲突工资之和最小的任务,处理完之后将该任务归类。

    其实处理(寻找和该任务匹配的最小工资和)和归类是可以分开进行的处理看的是左端点,归类起作用的是右端点。

    这样我们就不以线段为基本的处理单元了,改成以点为基本的处理单元,但是任务时长工资这些信息是和线段关联的,所以我们需要一个结构体来将点和线段关联上,这样就可以以点为基本单元进行处理了。

    按照点的时间排序

    如果是左端点,就去找和这个线段(点和线段由结构体关联上了)匹配的任务的最小时间,这个时候在剩余工资的那个类里面全是可行解,只需要维护一个当前最小时间的值就行。

    如果是右端点则去更新这个线段所在类别的时间最小值.(更新最小值其实就是相当于是前面的将这个任务加入类中)

  • 代码

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<map>
#include<algorithm>
#include<set>
#include<stack>
using namespace std;
const int INF=99999999;
int n,m;
int dp[200005];
struct Node
{
      int loc;
      int bj;//bj为0表示左端点为1表示右端点
      int time;
      int money;
}node[400005];
int nodecount;
void Init()
{
      nodecount=0;
      for(int i=0;i<=200000;i++)dp[i]=INF;
}
void Add_node(int x,int y,int c)
{
    node[++nodecount].loc=x;
    node[nodecount].bj=0;
    node[nodecount].time=y-x+1;
    node[nodecount].money=c;

    node[++nodecount].loc=y;
    node[nodecount].bj=1;
    node[nodecount].time=y-x+1;
    node[nodecount].money=c;
}
void In()
{
      int x,y,c;
      for(int i=1;i<=n;i++)
      {
            scanf("%d%d%d",&x,&y,&c);
            Add_node(x,y,c);
      }
}
int cmp(struct Node a,struct Node b)
{
      if(a.loc !=b.loc)return a.loc < b.loc;
      else return a.bj < b.bj;///先处理左端点,这样可以保证相邻的线段不会合在一起
}
void Work()
{
    int ans=INF;
    sort(node+1,node+nodecount+1,cmp);
    for(int k=1;k<=nodecount;k++)
    {
          if(node[k].bj==0)///左端点
          {
                ans=min(ans,dp[m-node[k].money]+node[k].time);
          }
          else
          {
                dp[node[k].money]=min(dp[node[k].money],node[k].time);
          }
    }
    if(ans!=INF)cout<<ans<<endl;
    else cout<<"oh no! "<<endl;
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
          Init();
          In();
          Work();
    }
    return 0;
}
/*
2 10
1 2 3
2 3 7
*/

I题 coach’s plan(二分+二分图匹配)

  • 题目大意

    有n个学生,m台电脑,每个学生使用每台电脑都对应一个适合值,现在让你给每个学生分配一台电脑(每台电脑仅可以分配给一个学生),问这些学生中最小的适合值最大是多少。

  • 分析

    一般这种问最小值最大的问题很容易想到二分,这里我们对这个最小的适合值进行二分,然后问题就转化成了给定一个最小的适合值是否存在可行解的判定问题了。注意:这里最小的适合值表示一个下界不一定要取到。

    n个学生m台电脑对应一个二维网格,题目的要求可以转化成以下这个问题:

    二维格子中每一行选且仅选一个,每一列至多选一个。

    给定了最小适合值后我们可以将小于这个值的格子划掉,然后在剩下的网格中判断是否存在可行解,因为行与列是一一对应的所以可以转化成一个匹配问题,又要求每一行必须选一个,所以就是二分图的最大匹配问题了。
    这里写图片描述

    一开始思考的时候是在二维网格的基础上思考的,这样就还需要做一个二维网格到二分图的思维转换,而如果一开始就从左边n个点,右边m个点的网络图出发进行思考的话就更容易想到二分图匹配。

  • 代码

  #include<cstdio>
  #include<iostream>
  #include<cmath>
  #include<cstring>
  #include<cstdlib>
  #include<queue>
  #include<map>
  #include<algorithm>
  #include<set>
  #include<stack>
  using namespace std;
  const int INF=99999999;
  int n,m;
  int minm;//图中最小的适合度
  int maxm;
  int a[505][505];
  void In()
  {
        minm=INF;
        maxm=0;
        for(int i=0;i<n;i++)
            for(int j=0;j<m;j++)
            {
                scanf("%d",&a[i][j]);
                minm=min(minm,a[i][j]);
                maxm=max(maxm,a[i][j]);
            }
  }
  struct Edge
  {
      int v;
      int next;
  }edge[500005];
  int edgecount;
  int head[10005];
  int match[10005];
  bool used[10005];
  void Init()
  {
        edgecount=0;
        memset(head,-1,sizeof(head));
  }
  void add_edge(int u,int v)
  {
       edge[++edgecount].v=v;
       edge[edgecount].next=head[u];
       head[u]=edgecount;
  }

  bool dfs(int u)
  {
      used[u]=true;
      for (int k=head[u];k!=-1;k=edge[k].next)
      {
          int v=edge[k].v;//考虑(u,v)这条边
          int w=match[v];
          if (w<0 || !used[w]&&dfs(w))//v还未匹配或者是从与v匹配的点出发能够找到增广路
          {
              match[u]=v;
              match[v]=u;
              return true;
          }
      }
      return false;
  }
  int bi_matching()
  {
      int res=0;
      memset(match,-1,sizeof(match));
      for (int x=0;x<n;x++)
      {
          if (match[x]<0)//x还没匹配
          {
              memset(used,0,sizeof(used));
              if (dfs(x))//从x出发找到一条增广路
                  res++;
          }
      }
      return res;
  }

  bool Check(int x)
  {
      Init();
      for(int i=0;i<n;i++)//列从0到n-1编号,行从n到n+m-1编号
      {
          for(int j=0;j<m;j++)
          {
              if(a[i][j]>=x)
              {
                    add_edge(i,j+n);
                    add_edge(j+n,i);
              }
          }
      }
      if(bi_matching()==n)return 1;
       return 0;
  }
  int Erfen()
  {
        int ans;
        int l=0;
        int r=1001;
        while(r-l>1)
        {
              int m=(l+r)/2;
              if(Check(m))l=m;
              else r=m;
        }
        return ans=l;
  }
  int main()
  {
      while(scanf("%d%d",&n,&m)!=EOF)
      {
            In();
            cout<<Erfen()<<endl;
      }
      return 0;
  }
  /*
  3 3
  1 2 5
  3 4 5
  5 5 5
  */
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值