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
  */
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章