2020牛客寒假算法基础集训营6

链接:link
来源:牛客网

A 配对

题目描述

现在有正整数集合 A 和 B A 和 B AB,每个集合里有 N 个数,你要建立他们间的一一映射 将每对配对的数字相加可以得到 N 个和,你要做的就是最大化第 K 大的和 1 ≤ K ≤ N ≤ 100 , 000 1≤K≤N≤100,000 1KN100,000 输入的所有数字不超过 1 0 8 10^8 108

输入描述

第一行 2 个数字 N , K N,K N,K接下来两行,每行 N 个正整数,分别表示 A 和 B 中的元素

思路

要让第K大的数最大,我们肯定是用 a 1 , a 2 , . . . , a k , b 1 , b 2 , . . . , b k a_1,a_2,...,a_k,b_1,b_2,...,b_k a1,a2,...,ak,b1,b2,...,bk这2k个数组成的k个和中的最小值。我们要让这个最小值最大,只能是 a 1 + b k , a 2 + b k − 1 , . . . , a k + b 1 a_1+b_k,a_2+b_{k-1},...,a_k+b_1 a1+bk,a2+bk1,...,ak+b1这k个数中的最小值,否则一定会更小。

AC代码

#include
using namespace std;
#define ll long long
#define rep(i,s,t) for(int i = s;i <= t;i++)
#define per(i,t,s) for(int i = t;i >= s;i--)
#define lb(x) x&(-x) 
#define maxn 100005
#define pi acos(-1)
#define eps 1e-8
#define mod 1000000007
int n, k;
int a[maxn], b[maxn], c[maxn];
bool cmp(int x,int y){    
return x > y;
}
int main(){    
scanf("%d%d",&n,&k);    
rep(i,1,n) scanf("%d",&a[i]);    
rep(i,1,n) scanf("%d",&b[i]);    
sort(a+1,a+n+1,cmp);    
sort(b+1,b+1+n,cmp);    
rep(i,1,k) c[i] = a[i] + b[k + 1 - i];    
sort(c+1,c+1+k);    
printf("%d\n",c[1]);    
return 0;
}

B 图

题目描述

现在有一个N个点的有向图,每个点仅有一条出边
你需要求出图中最长的简单路径包含点的数量 ( 1 ≤ N ≤ 1 , 000 , 000 ) (1≤N≤1,000,000) 1N1,000,000

输入描述

第一行一个数字 N N N
接下来 N N N行,每行一个正整数,第 i + 1 i+1 i+1行的数字表示第 i i i个点出边终点的编号
(点从1开始标号)

思路

遍历起点,递归的时候存储以这个点为起点的路径长度,注意遇到环要把环上所有点的值赋值成相同值,复杂度 O ( n ) O(n) O(n)

AC代码

#include
#include
#include
#define ll long long
#define maxn 1000005
using namespace std;
int n,ans,h[maxn],p[maxn];
int vis[maxn];
void findm(int id){
//  printf("%d\n",id);
    if(h[p[id]]==0){
        int res=1;
        int k=p[id];
        while(k!=id){
        //  printf("%d\n",k);
            res++;
            k=p[k];
        }
        h[k]=res;
        vis[k]=1;
        k=p[k];
        while(k!=id){
        //  printf("%d\n",k);
            h[k]=res;
            vis[k]=1;
            k=p[k];
        }
        return ;
    }
    h[id]=0;
    if(vis[p[id]]==0){
        findm(p[id]);
        if(vis[id]==0){
            h[id]=h[p[id]]+1;
            vis[id]=1;
        }
    }
    else if(vis[p[id]]=1){
        h[id]=h[p[id]]+1;
        return ;
    }
    return;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&p[i]);
    }
    for(int i=1;i<=n;i++)h[i]=-1;
    ans=0;
    for(int i=1;i<=n;i++){
        if(vis[i]==0)findm(i);
        ans=max(ans,h[i]);
    }
    printf("%d",ans);
    return 0;
}

C 汉诺塔

题目描述

现在你有 N N N 块矩形木板,第 i i i 块木板的尺寸是 X i ∗ Y i Xi*Yi XiYi,你想用这些木板来玩汉诺塔的游戏。 我们知道玩汉诺塔游戏需要把若干木板按照上小下大的顺序堆叠在一起,但因为木板是矩形,所以有一个问题: 第 i 块木板能放在第 j 块木板上方当且仅当 X i < X j XiXi<Xj Y i < Y j YiYi<Yj,于是你很可能没法把所有的木板按照一定的次序叠放起来。 你想把这些木板分为尽可能少的组,使得每组内的木板都能按照一定的次序叠放。 你需要给出任意一种合理的分组方案。 提醒:“任意”意味着你的答案不必和标准输出完全一致,只要正确即可。

输入描述

第一行,一个正整数 N N N接下来 N N N 行,每行两个正整数表示 X i Xi Xi Y i Yi Yi对于所有的数据, 1 ≤ N ≤ 100 , 000 , 1 ≤ X i , Y i ≤ N 1≤N≤100,000,1≤Xi,Yi≤N 1N100,0001Xi,YiN,Xi 互不相等且 Yi 互不相等

思路

把x作为第一关键字,y作为第二关键字排序,然后贪心匹配即可。

AC代码

#include
using namespace std;
#define ll long long
#define rep(i,s,t) for(int i = s;i <= t;i++)
#define per(i,t,s) for(int i = t;i >= s;i--)
#define lb(x) x&(-x) 
#define maxn 200005
#define pi acos(-1)
#define eps 1e-8
#define mod 1000000007
int n;
int t[maxn],f[maxn],m,s[maxn];  
struct node{    
int x,y,ind;
}a[maxn];  
void ins(int x,int v){    
for(;x <= n;x += lb(x)) t[x] += v;
}  
int qsum(int x){    
int res = 0;    
for(;x;x -= lb(x)) res += t[x];    
return res;
} 
 bool cmp(struct node a,struct node b){    
 if(a.x == b.x) return a.y < b.y;    
 return a.x < b.x;
 }  
 int find(int x){    
 int l = 1, r = a[x].y - 1;    
 while(l < r){        
 int mid = (l + r)>>1;        
 if(qsum(mid) == qsum(r)) r = mid;        
 else l = mid + 1;    }    return s[l];
 }  
 int main(){     
 scanf("%d",&n);    
 rep(i,1,n) scanf("%d%d",&a[i].x,&a[i].y),a[i].ind = i;    
 sort(a+1,a+1+n,cmp);    
 rep(i,1,n){        
 if(!qsum(a[i].y-1)) f[a[i].ind] = ++m;        
 else{            
 int pos = find(i);            
 f[a[i].ind] = f[a[pos].ind];            
 ins(a[pos].y,-1);        
 }        
 ins(a[i].y,1);        
 s[a[i].y] = i;    
 }    
 printf("%d\n",m);    
 rep(i,1,n) printf("%d ",f[i]);    
 return 0;
 }

D 重排列

题目描述

一个序列的重排列是指对这个序列中的元素进行若干次(包括0次)交换操作后得到的新序列 在本题中,序列中可能出现重复的数字,他们被视作不同的元素 例如,序列1 1的重排列有两种 现在有两个长度为 N 的非负整数序列 A 和 B,问有多少种 A 的重排列满足对于所有的 1 ≤ i ≤ N , 有 A i ≤ B i 1≤i≤N,有Ai≤Bi 1iNAiBi 由于答案可能很大,你只需要输出答案对1e9+7取模的结果

输入描述

输入第一行,包含一个正整数 N接下来一行,N 个非负整数表示序列 A再接下来一行,N 个非负整数表示序列B
1 ≤ N ≤ 100 , 000 , 0 ≤ A i , B i ≤ 1 0 9 1≤N≤100,000,0≤Ai,Bi≤10^9 1N100,000,0Ai,Bi109

思路

先离散化,然后将A,B从大到小排序。因为对于大的A能匹配的B,小的A一定能匹配。然后计数即可。

AC代码

#include
using namespace std;
#define ll long long
#define rep(i,s,t) for(int i = s;i <= t;i++)
#define per(i,t,s) for(int i = t;i >= s;i--)
#define lb(x) x&(-x) 
#define maxn 200005
#define pi acos(-1)
#define eps 1e-8
#define mod 1000000007
int n; 
int a[maxn], b[maxn],t[maxn]; 
void ins(int x,int v){    
for(;x <= 2*n;x += lb(x)) t[x] += v;
} 
int qsum(int x){    
int res = 0;    
for(;x;x -= lb(x)) res += t[x];    
return res;
} 
struct node{    
int val,ind,edge;
}c[maxn]; 
bool cmp(struct node x,struct node y){    
return x.val < y.val;
} 
int main(){    
int  m = 1;    
scanf("%d",&n);    
rep(i,1,n) scanf("%d",&a[i]);    
rep(i,1,n) scanf("%d",&b[i]);    
sort(a+1,a+1+n);    
sort(b+1,b+1+n);    
rep(i,1,n) c[i].val = a[i],c[i].ind = i,c[i].edge = 0;    
rep(i,n+1,2*n) c[i].val = b[i-n],c[i].ind = i - n,c[i].edge = 1;    sort(c+1,c+1+2*n,cmp);    
rep(i,1,2*n-1){        
if(c[i].val == c[i+1].val) c[i].val = m;        
else c[i].val = m++;    
}    
c[2*n].val = m;    
rep(i,1,2*n){        
if(c[i].edge == 0) a[c[i].ind] = c[i].val;        
else b[c[i].ind] = c[i].val;    
}    
rep(i,1,n) ins(b[i],1);    
//rep(i,1,2*n) printf("%d\n",t[i]);    
ll ans = 1;    
per(i,n,1){    
int x = qsum(2*n)-qsum(a[i]-1) -n + i;    
if(x < 0) ans = 0;    
ans = ans*x%mod;        
}     
printf("%lld\n",ans);    
return 0;
}

E 立方数

题目描述

对于给定的正整数 N,求最大的正整数 A,使得存在正整数 B,满足 A3B=N 输入包含 T 组数据, 1 ≤ T ≤ 10 , 000 ; 1 ≤ N ≤ 1 0 18 1≤T≤10,000;1≤N≤10^{18} 1T10,0001N1018

输入描述

第一行数字 T 表示数据组数接下来一行,T 个正整数 N

思路

首先肯定是要质因数分解的,但是小于1e6的质数约有 8e4个,时间复杂度不够。因此对于大得质数我们可以通过二分进行优化。

AC代码

#include
using namespace std;
#define ll long long
#define rep(i,s,t) for(int i = s;i <= t;i++)
#define per(i,t,s) for(int i = t;i >= s;i--)
#define lb(x) x&(-x) 
#define maxn 1000005
#define pi acos(-1)
#define eps 1e-8
#define mod 1000000007
int t, prime[maxn],tot_prime, vis[maxn];
ll n,num[maxn];
ll ksm(ll a,ll x){
 ll res = 1;
 while(x){
  if(x&1) res = res * a;
  a = a * a;
  x >>= 1;
 }
 return res;
}
void get(){
 rep(i,2,40005){
  if(!vis[i]) prime[++tot_prime] = i;
  for(int j = 1;i * prime[j] <= 40005 && j <= tot_prime;j++){
   vis[i*prime[j]] = 1;
   if(i % prime[j] == 0) break;
  }
 }
 int main(){
 get();
    scanf("%d",&t);
    while(t--){
     scanf("%lld",&n);
        int m = 0;
        ll ans = 1;
        rep(i,1,tot_prime){
         while(n%prime[i]==0) n /= prime[i], num[i]++; 
        }
        rep(i,1,tot_prime){
         ans *= ksm(prime[i],num[i]/3);
         num[i] = 0;
        }
        ll l = 1,r = 1e6;
  while(l <= r){
   ll mid = (l + r)>>1;
   if(mid*mid*mid == n){
    ans *= mid;
    break;
   }
   else if(mid*mid*mid < n) l = mid + 1;
   else r = mid - 1;
  } 
        printf("%lld\n",ans);
    }
 return 0;
}
}

F 十字阵列

题目描述

小 Q 新学会了一种魔法,可以对一个 N N N M M M列 的网格上的敌人造成伤害
第 i 次使用魔法可以对网格上的一个十字形区域(即第 x i x_i xi 行和第 y i y_i yi 列的并)中的每个格子上的敌人造成 z i z_i zi 点伤害
现在小 Q 一共使用了 H 次魔法,你需要在所有的施法完成之后统计造成伤害的情况,详见输出描述
提醒:本题输入规模较大,请使用高效的输入方式
1 ≤ H ≤ 500000 , 1 ≤ x i , y i , z i , N , M ≤ 2000 , 1 ≤ x i ≤ N , 1 ≤ y i ≤ M 1≤H≤500000 ,1≤x_i,y_i,z_i,N,M≤2000, 1≤xi≤N,1≤yi≤M 1H500000,1xi,yi,zi,N,M2000,1xiN,1yiM

输入描述

第一行 3 个数字 N,M,H
接下来 H 行,每行 3 个正整数 x i , y i , z i x_i,y_i,z_i xiyizi

思路

每次施魔法计算贡献即可

AC代码

#include
#include
#define maxn 1000005
#define ll long long
#define mod 1000000007
using namespace std;
int n,m,h,x,y,z;
ll ans;
int main(){
    scanf("%d%d%d",&n,&m,&h);
    ll sum1=(1+m)*m/2;
    ll sum2=(1+n)*n/2;
    while(h--){
        scanf("%d%d%d",&x,&y,&z);
        ans+=(m*x+sum1+n*y+sum2-x-y)*z;
        ans%=mod;
    }
    printf("%lld",ans);
    return 0;
}

G 括号序列

题目描述

合法括号序列的定义是:
1.空序列是合法括号序列
2.如果 S 是一个合法括号序列,那么(S)是合法括号序列
3.如果 A 和 B 都是合法括号序列,那么 AB 是一个合法括号序列
现在给定一个括号序列,求最少删去几个括号能得到一个合法的括号序列
输入包含 T 组数据,每组数据中,设括号序列的长度为 N
1 ≤ T , ∑ N ≤ 1 , 000 , 000 1≤T,\sum N≤1,000,000 1T,N1,000,000
(由于空串是合法的括号序列,所以答案可以是N)

输入描述

第一行一个数字 T
接下来 T 组数据共 2T 行,每组数据第一行是 N
第二行则是一个长度为 N 的括号序列

思路

签到题,栈匹配括号

AC代码

#include
#include
#define maxn 1000005
using namespace std;
int t,ans;
char str[maxn];
int n;
int cnt;
int main(){
    scanf("%d",&t);
    while(t--){
        ans=0;
        cnt=0;
        scanf("%d",&n);
        scanf("%s",str);
        for(int i=0;i<n;i++){
            if(str[i]==')'){
                if(cnt!=0)cnt--;
                else ans++;
            }
            else cnt++;
        }
        ans+=cnt;
        printf("%d\n",ans);
    }
    return 0;
}

I 导航系统

题目描述

小 Q 所在的国家有 N 个城市,城市间由 N-1 条双向道路连接,任意一对城市都是互通的。
每条道路有一个长度,自然,小 Q 的导航系统能显示每对城市间的最短距离。
但是小 Q 对这个系统并不太放心,于是他向你求助:
给定每对城市间的最短距离,你要判断距离表是否一定有误。
如果这张距离表是自洽的,那么请你按升序依次给出每条道路的长度。
对于全部的数据, 1 ≤ N ≤ 500 1≤N≤500 1N500,输入的所有数字都是不超过 1 0 9 10^9 109 的非负整数。

输入描述

第一行一个数字 N
接下来 N 行,每行 N 个正整数
第 i 行第 j 列的数字表示城市 i 和城市 j 间的最短距离
保证第 i 行第 i 列的数字为 0

思路

考虑最短路的求法,先确定最短边,然后向外衍生,又由题可得整个图是一棵树,所以我们可以求最小生成树,然后每对在一个并查集内的点之间的边判断是不是用建好的边生成的最短路,即可。

AC代码

#include
#include
#include
#include
#include
#define maxn 250005
#define ll long long
using namespace std;
int n,cnt,x;
struct P{
    int x,y,dis;
}a[maxn];
int fa[505],res[505],cntr;
int map[505][505];
bool cmp(P a,P b){
    return a.dis<b.dis;
}
int get_fa(int x){
    if(fa[x]==x)return x;
    else return fa[x]=get_fa(fa[x]);
}
void reset_fa(int num){
    for(int i=1;i<=num;i++)fa[i]=i;
    return ;
}
void reset_map(int num){
    for(int i=1;i<=num;i++){
        for(int j=1;j<=num;j++){
            if(i==j)map[i][j]=0;
            else map[i][j]=-1;
        }
    }
    return ;
}
 
int main(){
    scanf("%d",&n);
    reset_fa(n);
    reset_map(n);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(i==j){
                scanf("%d",&x);
                continue;
            }
            cnt++;
            scanf("%d",&a[cnt].dis);
            a[cnt].x=i,a[cnt].y=j;
        }
    }
    sort(a+1,a+cnt+1,cmp);
    for(int i=1;i<=cnt;i++){
        int nowx=a[i].x,nowy=a[i].y,nowd=a[i].dis;
//      printf("%d %d %d\n",nowx,nowy,nowd);
        int fax=get_fa(nowx),fay=get_fa(nowy);
        if(fax!=fay){
            fa[fax]=fay;
            res[++cntr]=nowd;
            for(int i=1;i<=n;i++){
                if(map[nowx][i]==-1)continue;
                for(int j=1;j<=n;j++){
                    if(map[nowy][j]==-1)continue;
                    if(map[i][j]!=-1)continue;
                    map[i][j]=map[nowx][i]+map[nowy][j]+nowd;
                    map[j][i]=map[nowx][i]+map[nowy][j]+nowd;
                }
            }
//          for(int i=1;i<=n;i++){
//              for(int j=1;j<=n;j++){
//                  printf("%d ",map[i][j]);
//              }
//              printf("\n");
//          }
        }
        else{
            if(map[nowx][nowy]==nowd)continue;
            else {
                printf("No\n");
                return 0;
            }
        }
    }
    printf("Yes\n");
    for(int i=1;i<=cntr;i++){
        printf("%d\n",res[i]);
    }
    return 0;
}

纪念一下第一次拿一血
2020牛客寒假算法基础集训营6_第1张图片

H 云

题目描述

现在天空(可视为二维平面)中有 N 朵 A 类云,M 朵 B 类云,每朵云的形状都可以用边平行于坐标轴的矩形来描述。
一开始,A 类云在第三象限,B 类云在第一象限,没有任何云和坐标轴有交点。
随着风的吹拂,A 类云以每秒一个单位的速度向右移动,B 类云以每秒一个单位的速度向下移动,当一朵 A 类云和一朵 B 类云在某一个时刻有了至少一个公共点,它们就相遇了。
现在请你告诉小 R,有多少对 A 类云和 B 类云能够相遇。
1≤N,M≤100,000,1≤|Xi|,|Yi|,|Pi|,|Qi|≤109
注:
1.本题输入规模较大,请注意输入的效率
2.输入的云的形状可能为退化的矩形(直线或点)

思路

A类云与B类云都以相同速率成直角方向移动。所以将 x x x y y y坐标相减可以得到相对位置。upper_bound(bl+1,bl+m+1,ar[i])求出第一个比当前A类云左上顶点高的B类云右下顶点(指 x − y x-y xy),既然右下顶点比左上都高,那么必定不会相遇, p p p则为对于第 i i i个A类云来说,比Ai高的B类云个数。同理, q q q等于比Ai低的B类云个数。 m − p − q m-p-q mpq则为与Ai相交的B类云个数。

AC代码

#include
#include
#include
#include
#include
#include
#define MOD 1000000007
#define maxn 100120
#define INF 2147483647
typedef unsigned float_bits;

using namespace std;

//priority_queue ,greater > que;

#define ll long long
#define rep(i,s,t) for(int i = s;i <= t;i++)
#define per(i,t,s) for(int i = t;i >= s;i--)
#define lb(x) x&(-x) 
#define pi acos(-1)
#define eps 1e-8
#define mod 1000000007

int n,m,a,b,c,d;
int al[maxn],ar[maxn],bl[maxn],br[maxn];
long long p,q,ans;

int main()
{
    int i,j,k;
    scanf("%d%d",&n,&m);
    for (i=1;i<=n;i++)
    {
        scanf("%d%d%d%d",&a,&b,&c,&d);
        al[i]=a-b; ar[i]=c-d;
    }
    for (i=1;i<=m;i++)
    {
        scanf("%d%d%d%d",&a,&b,&c,&d);
        bl[i]=a-b; br[i]=c-d;
    }
    sort(bl+1,bl+m+1);
    sort(br+1,br+m+1);
    ans=0;
    for (i=1;i<=n;i++)
    {
        p=bl+m+1-upper_bound(bl+1,bl+m+1,ar[i]);
        q=lower_bound(br+1,br+m+1,al[i])-br-1;
        ans+=m-p-q;
    }
    printf("%lld",ans);

    return 0;
}

J 签到题

题目描述

现有一个边长为正整数的三角形,问能否以其三个顶点为圆心画三个圆,使三个圆两两外切 三边长均不超过 1 0 8 10^8 108

输入描述

三个正整数,表示三角形的边长

思路

r 1 + r 2 = a , r 2 + r 3 = b , r 3 + r 1 = c r_1+r_2=a,r_2+r_3=b,r_3+r_1=c r1+r2=a,r2+r3=b,r3+r1=c,解方程即可。

AC代码

#include
using namespace std;
#define ll long long
#define rep(i,s,t) for(int i = s;i <= t;i++)
#define per(i,t,s) for(int i = t;i >= s;i--)
#define lb(x) x&(-x) 
#define maxn 100005
#define pi acos(-1)
#define eps 1e-8
#define mod 1000000007
double r[10];
int main(){    
double x, y, z;    
scanf("%lf%lf%lf",&x,&y,&z);    
if(x + y <= z || x + z <= y || y + z <= x) printf("wtnl\n");    
else{        
r[1] = (x + y - z)/2;        
r[2] = (y + z - x)/2;       
 r[3] = (z + x - y)/2;        
 sort(r+1,r+4);        
 printf("Yes\n");        
 printf("%.2lf %.2lf %.2lf\n",r[1],r[2],r[3]);    
 }    
 return 0;
 }

你可能感兴趣的:(算法)