算法训练计划(第四天)

之前也有训练,只是没时间写博客了,今天正好有时间,来写一下

洛谷 —— 分组

P4447 [AHOI2018初中组] 分组 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

上来就是一道绿题啊

本题是要把一些人分成几组,然后使得人数最少的组人最多,也就是最小的最大值,我们应该可以想到二分和队列。因为他每组中人数的实力必须是连续的,我们可以定义一个数组来记录每组当前实力的最大值。

1、我们先对实力进行排序

cin>>n;
  for(int i =1;i<=n;++i)cin>>a[i];
  sort(a+1 , a+1+n);

2、记录每一组实力最大的数,然后枚举每一个ai,找到某一组的实力最大的数是ai-1,之后ai就可以进入这一组,同时用sz记录每一组的人数

for(int i =1;i<=n;++i){
    int j = search(a[i]-1); //j表示找到ai -1的这个实力在第j组
    
    if(j == -1 || cnt == 0){  //如果没有找到,或者cnt还等于0 , 就新开一组 , cnt是有多少组
        ma[cnt] = a[i];
        sz[cnt++]++;
    }
    else {
        ma[j] = a[i];
        sz[j]++;
    }
}

3、二分查找

int search(int x){
  int l = 0 , r = cnt-1;
  while(l < r){
    int mid = (l+r+1)>>1;
    if(ma[mid] <= x)l = mid;
    else r = mid-1;
  }
  return ma[l] ==x ? l : -1;
}

之后这题就基本解决了,以下是完整代码

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define INF 0x3f3f3f3f
const double pi = 3.14;
// #define x first
// #define y second 
#define int long long
// #define ll long long
typedef pair<int,int> pii;
// const int mod = 998244353;
const int N = 1e5+9;
int  a[N];
bool vis[N];
int n;
int ma[N] , sz[N];
int cnt;

int search(int x){
  int l = 0 , r = cnt-1;
  while(l < r){
    int mid = (l+r+1)>>1;
    if(ma[mid] <= x)l = mid;
    else r = mid-1;
  }
  return ma[l] ==x ? l : -1;
}

void solve(){
  az
  
  for(int i =1;i<=n;++i){
    int j =search(a[i] - 1);  //找比ai小一的数

    if(j == -1 || cnt == 0){
      ma[cnt] = a[i];
      sz[cnt++]++;
    }
    else {
      ma[j] = a[i];
      sz[j]++;
    }
    
		
  }
 
  
  int maxn = INF;
  for(int i =0;i<cnt;++i)maxn = min(maxn , sz[i]);
  cout<<maxn;
   
}


signed main(){
    ios::sync_with_stdio(false);
    cout.tie(0);
    cin.tie(0);
    int _  = 1;
	
    while(_--){
      solve();
    }
    
    return 0;
}

洛谷 —— 国王游戏

P1080 [NOIP2012 提高组] 国王游戏 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

这题是一道找规律的贪心题,但是还需要高精度才能通过,由于我不会高精度,所以这里给出60分代码

1、数学推导:

假设只有两个大臣 此时的最多的大臣的金币是max( ai/bi , (a[i]+a[i+1])/b[i+1]) 

若将两个人交换位置 , 此时最多的金币是  max(a[i+1]/b[i+1] , (a[i+1] + a[i])/b[i])

那么交换前后哪个数比较小呢 ? 也就是比较  (a[i]+a[i+1])/b[i+1] 和 (a[i+1] + a[i])/b[i] 这两个数的大小,可以看到,这两个数只有分母是不同的,也就是说 ,如果说后面的人右手越大,就越不要交换,什么意思呢?也就是按照右手大小排序,右手越小的就越要在前面,那么这题的关键就找到了,就是对右手从小到大排序。

2、排序。 可以用结构体

struct hand{
  int l ,r;
}a[N];

bool cmp(hand x , hand y){
  return x.l * x.r < y.l * y.r;
}

cin>>n;
  cin>>L>>R;
  int ans = -1;
  for(int i =1;i<=n;++i)cin>>a[i].l>>a[i].r;
  sort(a+1 , a+1+n ,cmp);

完整代码

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define INF 0x3f3f3f3f
const double pi = 3.14;
// #define x first
// #define y second 
#define int long long
// #define ll long long
typedef pair<int,int> pii;
// const int mod = 998244353;
const int N = 1e5+9;
int n;
int L , R;

struct hand{
  int l ,r;
}a[N];

bool cmp(hand x , hand y){
  return x.l * x.r < y.l * y.r;
}



void solve(){
  cin>>n;
  cin>>L>>R;
  int ans = -1;
  for(int i =1;i<=n;++i)cin>>a[i].l>>a[i].r;
  sort(a+1 , a+1+n ,cmp);
  // for(int i =1;i<=n;++i){
  //   cout<<a[i].l<<' '<<a[i].r<<endl;
  // }
  int sum = L;
  for(int i =1;i<=n;++i){
    sum *= a[i].l;
    int t = sum/(a[i].r*a[i].l);
    ans = max(ans , t);
  }
  cout<<ans;
  

  

}


signed main(){
    ios::sync_with_stdio(false);
    cout.tie(0);
    cin.tie(0);
    int _  = 1;
	
    while(_--){
      solve();
    }
    
    return 0;
}

洛谷 —— 单词接龙

这题可以用一个dfs,那么如何dfs有两个问题 : 一是如何判断谁跟谁可以接龙 二是每个单词都只次接龙的机会

1、我们可以先预处理一下所有的单词,用g[i][j]表示j可以接在i后面 , 就类似图论中的有向图一样


for(int i =1;i<=n;++i){
    for(int j =1;j<=n;++j){
        string x =a[i] , y = a[j];
        for(int k =1;k<=min(x.size() , y.size());++k){
            if(x.substr(x.size()-k , k) == y.substr(0,k)){
                g[i][j] = k;
                break;
            }
        }
    }
}

2、我们可以用一个数组来记录每个单词接龙次数

那么完整代码如下

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define INF 0x3f3f3f3f
const double pi = 3.14;
// #define x first
// #define y second 
#define int long long
// #define ll long long
typedef pair<int,int> pii;
// const int mod = 998244353;
const int N = 25;
int n , res;
string a[N];
int used[N];
int g[N][N];

void dfs(string dargon , int x){
  res = max(res , (int)dargon.size());
  used[x]++;

  for(int i =1;i<=n;++i){
    if(g[x][i] && used[i] < 2){
      dfs(dargon + a[i].substr(g[x][i]) , i);
    }
  }
  used[x]--; //回溯
}


void solve(){
  cin>>n;
  for(int i =1;i<=n;++i)cin>>a[i];
  char st;cin>>st;

  for(int i =1;i<=n;++i){
    for(int j =1;j<=n;++j){
      string x = a[i] , y = a[j];
      for(int k =1;k<=min(x.size() , y.size())-1;++k)
        if(x.substr(x.size()-k , k) == y.substr(0,k)){
          g[i][j] = k;
          break;  //因为龙要长,所以重复的长度要短
        }
    }
  }

  for(int i =1;i<=n;++i){
    if(a[i][0] == st){
      dfs(a[i],i);
    }
  }
  cout<<res;

}


signed main(){
    ios::sync_with_stdio(false);
    cout.tie(0);
    cin.tie(0);
    int _  = 1;
	
    while(_--){
      solve();
    }
    
    return 0;
}

洛谷 —— 字串变换

P1032 [NOIP2002 提高组] 字串变换 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

由于最短我们可以想到用层序遍历的解法,那么这题就很好解决了。那么本题的难点就是字符串的变换,我们可以用find 和 replace来解决 

1、bfs部分内容

q.push({n,0});  //最开始是n,走了0步

  while(!q.empty()){
    auto now = q.front();
    q.pop();
    if(now.step >= 11){  //当大于等于11步的时候就返回-1了
      return -1;
    }
    if(now.s == m){  //找到字串后返回此时的步骤 , 由于是bfs,一定是最小的步骤
      return now.step;
    }
    for(int i =0;i<idx;++i){
      int id = now.s.find(a[i]);  //进行查找
      while(id != -1){ //注意是while
        
         string st = now.s;  //用一个临时字符串代替 , 因为主字符串不能变化
         st.replace(id,a[i].size(),b[i]);
         if(mp[st] == 0){
          q.push({st,now.step+1});
          mp[st]++;     //这里稍后在说
         }
        
        id = now.s.find(a[i],id+1);
      }
    }

  }
  return -1;

2、那么根据以上做法,我们会发现超出内存了,大家可以注意到上面的mp[st]++,这是干什么的? 因为bfs太多的字符串进入我们的队列后,超出了内存,而很多的字符串却是相同的,对于相同的字符串,我们只需要遍历一次。所以开一个哈希表来进行查重,这里使用的是map

完整代码

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define INF 0x3f3f3f3f
const double pi = 3.14;
// #define x first
// #define y second 
#define int long long
// #define ll long long
typedef pair<int,int> pii;
// const int mod = 998244353;
const int N = 15;
string a[N] , b[N];
map<string ,int >mp;
struct word
{
  string s;
  int step;
};
queue<word>q;
string n , m;
int idx;
int bfs(){
  q.push({n,0});  //最开始是n,走了0步

  while(!q.empty()){
    auto now = q.front();
    q.pop();
    if(now.step >= 11){
      return -1;
    }
    if(now.s == m){
      return now.step;
    }
    for(int i =0;i<idx;++i){
      int id = now.s.find(a[i]);
      while(id != -1){
        
         string st = now.s;
         st.replace(id,a[i].size(),b[i]);
         if(mp[st] == 0){
          q.push({st,now.step+1});
          mp[st]++;
         }
        
        id = now.s.find(a[i],id+1);
      }
    }

  }
  return -1;

}


void solve(){
  cin>>n>>m;  //要把n变成m
  while(cin>>a[idx]>>b[idx])idx++;
  int ans =  bfs();
  if(ans == -1)cout<<"NO ANSWER!";
  else cout<<ans;
  
}


signed main(){
    ios::sync_with_stdio(false);
    cout.tie(0);
    cin.tie(0);
    int _  = 1;
	
    while(_--){
      solve();
    }
    
    return 0;
}

洛谷 —— 路标设置

P3853 [TJOI2007] 路标设置 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

这题很显然最大的最小值,二分答案,就类似于进击的奶牛这道题进击的奶牛

 既然是二分答案,那么就很明确了,难点是如何进行判断

1、哪一种二分模板

我们可以想一下,空旷指数越大,那么路标也就更容易放完,也就是题意更好满足,空旷指数变小,题意就更难满足 , 所以说,我们需要找一个临界的右边点 (因为左边都是不满足的 , 右边都是满足的 ,找一个最小的满足点) 

while(l < r){
    int mid = (l+r)>>1;
    if(check())r = mid;
    else l = mid+1;
}

2、如何写check函数

我们可以通过路标的数量进行检查,我们可以先预处理出来一个差分数组,表示的就是两个路标之间的差值 , 如果路标之间的差值大于 x , 我们就加若干个路标,最后统计一下加的路标是否大于我们的题目中所给的路标数量

int cnt = 0;
  for(int i =1;i<=n;++i){
    if(dif[i] > x){
      cnt += (dif[i]/x) - (dif[i]%x == 0);
    }
    if(cnt > m )return false;
  }
  if(cnt <=m)return true;
  else return false;

完整代码

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define INF 0x3f3f3f3f
const double pi = 3.14;
// #define x first
// #define y second 
#define int long long
// #define ll long long
typedef pair<int,int> pii;
// const int mod = 998244353;
int l , n , m;
const int N = 100010;
int a[N] ,dif[N];

bool check(int x){
  int cnt = 0;
  for(int i =1;i<=n;++i){
    if(dif[i] > x){
      cnt += (dif[i]/x) - (dif[i]%x == 0);
    }
    if(cnt > m )return false;
  }
  if(cnt <=m)return true;
  else return false;
}


void solve(){
  cin>>l>>n>>m;
  for(int i =1;i<=n;++i)cin>>a[i];
  sort(a+1 , a+1+n);
  for(int i =1;i<=n;++i)dif[i] = a[i] - a[i-1];

  int ll = 0 , r = l;
  while(ll < r){
    int mid = (ll+r)>>1;
    if(check(mid))r = mid;
    else ll = mid+1;
  }
  cout<<r;

  //n-1份路 n-1份路加起来是l


}


signed main(){
    ios::sync_with_stdio(false);
    cout.tie(0);
    cin.tie(0);
    int _  = 1;
	
    while(_--){
      solve();
    }
    
    return 0;
}

  • 25
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值