2021牛客寒假算法基础集训营3

A:模数的世界

B:内卷

C:重力坠击(枚举)

  题目大意是给定 n ( 1 ≤ n ≤ 10 ) n(1\le n\le 10) n(1n10)个敌人的坐标 ( 0 ≤ ∣ x i ∣ , ∣ y i ∣ ≤ 7 ) (0\le|x_i|,|y_i|\le7) (0xi,yi7),每个敌人都有一个被消灭的范围 r i ( 1 ≤ r i ≤ 7 ) r_i(1\le r_i\le7) ri(1ri7);然后给定攻击者的攻击范围 R ( 1 ≤ R ≤ 7 ) R(1\le R \le7) R(1R7)以及攻击轮数 k ( 1 ≤ k ≤ 3 ) k(1\le k\le3) k(1k3),要求确定攻击者的坐标位置(必须是整数),问消灭最多的敌人数。
  坐标范围很小,很明显考虑枚举,因为每一次的枚举数到了 15 ∗ 15 = 225 15*15=225 1515=225,所以不适合用0/1位枚举,直接DFS即可。血泪教训:不要用sqrt()函数太多次,库函数速度慢,会导致TLE

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
struct Enermy{
    
    
    int x,y,r;
}e[15];
int n,k,R,maxnum=0;
int vis[15],mp[20][20],dx[5],dy[5];
inline int Distance(int x1,int y1,int x2,int y2){
    
    return (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2);}
inline void cal()
{
    
    
    int ans=0;
    for(int i=1;i<=n;++i) vis[i]=0;
    for(int num=1;num<=k;++num)
        for(int i=1;i<=n;++i)
        {
    
    
            if(vis[i] || Distance(dx[num],dy[num],e[i].x,e[i].y)>(R+e[i].r)*(R+e[i].r)) continue;
            else vis[i]=1,ans++;
        }
    if(ans>maxnum) maxnum=ans;
}
inline void DFS(int deep)
{
    
    
    if(deep>k) {
    
    cal();return;}
    else{
    
    
        for(int i=-7;i<=7;++i)
            for(int j=-7;j<=7;++j)
                if(mp[i+7][j+7]==0){
    
    
                    mp[i+7][j+7]=1;dx[deep]=i,dy[deep]=j;
                    DFS(deep+1);
                    mp[i+7][j+7]=0;
                }
    }
}
int main()
{
    
    
    scanf("%d%d%d",&n,&k,&R);
    for(int i=1;i<=n;++i) scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].r);
    DFS(1);
    printf("%d",maxnum);
}

D:Happy New Year!(C语言入门题)

#include<bits/stdc++.h>
using namespace std;
int get_num(int x)
{
    
    
    int ans=0;
    while(x) ans+=x%10,x/=10;
    return ans;
}
int main()
{
    
    
    int n,cur_sum,next,next_sum;cin>>n;
    cur_sum=get_num(n);next=n+1;
    while(1)
    {
    
    
        next_sum=get_num(next);
        if(next_sum==cur_sum) {
    
    cout<<next;break;}
        next++;
    }
}

E:买礼物(链表,线段树)

  题目大意是 n n n个箱子放了 n n n个礼物,有两种操作:①从第 i i i个箱子中取出礼物;②询问 [ x , y ] [x,y] [x,y]中是否有两个相同种类的礼物。
  很明显的单点修改和区间查询的问题,但关键问题是如何做到区间查询得到是否有不同种类的礼物。这里是首先采用链表的思想,用 l a s t [ i ] last[i] last[i]表示和第 i i i个礼物种类相同的上一个礼物的位置(没有则为0), n e x [ i ] nex[i] nex[i]表示和第 i i i个礼物种类相同的下一个礼物的位置(没有则为INF)。然后一个礼物的取走,就相当于链表的删除操作: l a s t [ n e x [ i ] ] = l a s t [ i ] , n e x [ l a s t [ i ] ] = n e x [ i ] last[nex[i]]=last[i],nex[last[i]]=nex[i] last[nex[i]]=last[i],nex[last[i]]=nex[i]我们用 n e x [ ] nex[] nex[]数组建立线段树(区间最小值),很明显一个区间的最小值如果小于区间右端点,就说明存在两个种类相同的礼物。

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
const int maxn=5e5+100;
const int INF=0x3f3f3f3f;
vector<int> v[maxn*2];
int n,q,a[maxn],last[maxn],nex[maxn],tree[maxn*4];
void build(int l=1,int r=n,int p=1)
{
    
    
    if(l==r) tree[p]=nex[l];
    else{
    
    
        int mid=(l+r)/2;
        build(l,mid,p*2);build(mid+1,r,p*2+1);
        tree[p]=min(tree[p*2],tree[p*2+1]);
    }
}
void update(int l,int r,int num,int cl=1,int cr=n,int p=1)
{
    
    
    if(cr<l || cl>r) return;
    if(cl==cr) tree[p]=num;
    else{
    
    
        int mid=(cl+cr)/2;
        update(l,r,num,cl,mid,p*2);
        update(l,r,num,mid+1,cr,p*2+1);
        tree[p]=min(tree[p*2],tree[p*2+1]);
    }
}
int query(int l,int r,int cl=1,int cr=n,int p=1)
{
    
    
    if(cr<l || cl>r) return INF;
    if(cl>=l && cr<=r) return tree[p];
    else{
    
    
        int mid=(cl+cr)/2;
        return min(query(l,r,cl,mid,p*2),query(l,r,mid+1,cr,p*2+1));
    }
}
int main()
{
    
    
    close;cin>>n>>q;
    for(int i=1;i<=1e6;++i) v[i].push_back(0);
    for(int i=1;i<=n;++i) cin>>a[i],v[a[i]].push_back(i);
    for(int i=1;i<=1e6;++i)
    {
    
    
        int size=v[i].size();
        if(size==1) continue;
        v[i].push_back(INF);
        for(int j=1;j<size;++j) last[v[i][j]]=v[i][j-1],nex[v[i][j]]=v[i][j+1];
    }
    build();
    while(q--)
    {
    
    
        int op;cin>>op;
        if(op==1){
    
    
            int pos;cin>>pos;
            if(nex[pos]!=INF) last[nex[pos]]=last[pos];//注意这个地方要特判,否则会造成越界
            nex[last[pos]]=nex[pos];
            update(pos,pos,INF);
            update(last[pos],last[pos],nex[pos]);
            last[pos]=0;nex[pos]=INF;
        }
        else{
    
    
            int x,y;cin>>x>>y;
            if(query(x,y)<=y) cout<<"1\n";
            else cout<<"0\n";
        }
    }
}

F:匹配串(思维)

  题目大意是给定 n n n个匹配串,仅由小写字母或者’#‘组成,’#‘代表能够匹配任意长度的序列,同时保证了每个匹配串中至少含有一个’#’。问能不能找到字符串和这 n n n个匹配串都匹配,如果不能则输出0,有无穷多个则输出-1,否则输出个数。
  简单的贪心题,只要每个匹配串的第一个’#‘前的前缀串和最后一个’#'后的后缀串能够能够匹配(不一定长度一样,但是没有出现不一致),就能够构造出无穷多个,否则输出-1.

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+100;
string s[maxn],before="",after="";bool ok=true;
void solve(int n)
{
    
    
    for(int i=0;i<s[1].length();++i) {
    
    if(s[1][i]!='#') before+=s[1][i];else break;}
    for(int i=s[1].length()-1;i>=0;--i){
    
    if(s[1][i]!='#') after+=s[1][i];else break;}
    int lenb=before.length(),lena=after.length();
    for(int i=2;i<=n;++i)
    {
    
    
        int p=0;
        for(int j=0;j<s[i].length();++j)
        {
    
    
            if(s[i][j]!='#'){
    
    
                if(p<lenb){
    
    if(before[p]!=s[i][j]) {
    
    ok=false;return;}p++;}
                else before+=s[i][j],p=++lenb;
            }
            else break;
        }
        p=0;
        for(int j=s[i].length()-1;j>=0;--j)
        {
    
    
            if(s[i][j]!='#'){
    
    
                if(p<lena){
    
    if(after[p]!=s[i][j]) {
    
    ok=false;return;}p++;}
                else after+=s[i][j],p=++lena;
            }
            else break;
        }
    } 
}
int main()
{
    
    
    int n;cin>>n;
    for(int i=1;i<=n;++i) cin>>s[i];
    solve(n);
    if(ok) cout<<-1;else cout<<0;
}

G:糖果(连通块)

  题目大意是给 n n n个小朋友分糖,他们有 m m m对朋友关系,要同时满足①不能低于 a i a_i ai;②分的躺不能比他朋友分的少。
  可以转化成图,有朋友关系就是有一条边相连,每个小朋友分的糖果数就是其所在的连通块中最大的 a i a_i ai.所以DFS跑一遍图,找到图中的各个连通块的大小以及连通块中 m a x ( a i ) max(a_i) max(ai)即可。

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
typedef long long ll;
const int maxn=1e6+100;
vector<int> v[maxn];int vis[maxn],a[maxn];
ll ans=0,num=0,maxnum=0;
void DFS(int root,int fa)
{
    
    
    int size=v[root].size();
    num++;vis[root]=1;if(a[root]>maxnum) maxnum=a[root];
    for(int i=0;i<size;++i)
    {
    
    
        if(v[root][i]==fa || vis[v[root][i]]) continue;
        DFS(v[root][i],root);
    }
}
int main()
{
    
    
    close;int n,m;cin>>n>>m;
    for(int i=1;i<=n;++i) cin>>a[i];
    for(int i=1;i<=m;++i){
    
    
        int x,y;cin>>x>>y;
        v[x].push_back(y);v[y].push_back(x);
    }
    for(int i=1;i<=n;++i)
        if(!vis[i]) num=maxnum=0,DFS(i,0),ans+=num*maxnum;
    cout<<ans;
}

H:数字串(构造)

  题目大意是给出一个只包括小写字母的字符串S,并假定’a’=1,‘b’=2…,‘z’=26.你需要找出另一个字符串T,满足按照上述规则转换出来的数字串和S转换出来的数字串保持一致。多种方案输出一种即可,不存在则输出-1.
  ①拆分:存在一个字母对应的数字比10大,同时不等于20时,我们将个位和十位拆开,分别转换成一个字母即可;②合并:如果两个字母对应的数字都是一位数,同时组合起来是一个不超过26的合法数字,那么就直接组合转换成一个字母。
  如果上述两种方式都做不到,说明没有合法的字符串,输出-1.

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false);
using namespace std;
bool ok=false;
int main()
{
    
    
    string T,S="";cin>>T;
    int len=T.length();
    for(int i=0;i<len;++i)
    {
    
    
        int num=T[i]-'a'+1;
        if(num>10 && num!=20){
    
    
            S+=(char)((num/10)-1+'a');S+=(char)((num%10)-1+'a');
            for(int j=i+1;j<len;++j) S+=T[j];
            ok=true;
            break;
        }
        else if(num<=2){
    
    
            if(i+1<len && (T[i+1]-'a'+1)<10 && num*10+(T[i+1]-'a'+1)<=26)
            {
    
    
                S+=(char)('a'+num*10+(T[i+1]-'a'));
                for(int j=i+2;j<len;++j) S+=T[j];
                ok=true;
                break;
            } 
        }
        S+=T[i];
    }
    if(ok) cout<<S;else cout<<-1;
}

I:序列的美观度(贪心/DP)

  题目大意是一个字符串的美观度是整数 i i i的数量,整数 i ( 1 ≤ i ≤ n ) i(1\le i\le n) i(1in)要满足 S i = S i + 1 S_i=S_{i+1} Si=Si+1.问对于给定的字符串S来说,他的所有子序列(不一定连续)中最大的美观度是多少。
  解法一:贪心的思想,找最近的两个相同数字拼接在当前序列末尾是最优的。设 x , y x,y x,y是当前最近的两个相同数字,那么在 x , y x,y x,y之间一定找不到任何相同的两个数字;假定 z z z是中间的某一个数,那么即使他和 y y y后面的某个数配对,贡献也不会超过当前的选择(都是1)。

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
const int maxn=1e6+100;
int loc[maxn],a[maxn];
map<int,int> mp;
int main()
{
    
    
    close;int n,x;cin>>n;
    for(int i=1;i<=n;++i){
    
    
        cin>>a[i];
        if(mp[a[i]]!=0) loc[i]=mp[a[i]];
        mp[a[i]]=i;
    }
    int lastpos=1,num=0;
    for(int i=1;i<=n;++i)
        if(loc[i]>=lastpos) lastpos=i,num++;
    cout<<num;
}

  赛后看到了题解中的一种做法。解法二:DP。我们令 d p [ i ] dp[i] dp[i]表示以 a [ i ] a[i] a[i]结尾的子序列的最大美观度,很容易得到转移方程是: d p [ i ] = m a x { d p [ j ] + 1 } ( j < i   & &   a [ i ] = a [ j ] ) dp[i]=max\{dp[j]+1\}(j<i\ \&\&\ a[i]=a[j]) dp[i]=max{ dp[j]+1}(j<i && a[i]=a[j]) d p [ i ] = m a x { d p [ j ] } ( j < i   & &   a [ i ] ≠ a [ j ] ) dp[i]=max\{dp[j]\}(j<i\ \&\&\ a[i]\ne a[j]) dp[i]=max{ dp[j]}(j<i && a[i]=a[j])如果直接这样写的时间复杂度是 O ( n 2 ) O(n^2) O(n2),但我们可以再第一个式子中就记录最近一个相等的位置,第二个式子直接对前 i − 1 i-1 i1项记录最大值,然后二者区最大即可。

J:加法和乘法(博弈)

  题目大意是给定 n n n个数,每次可以选择其中的两个数,将他们进行加法或乘法运算,然后仅保留运算后的结果。最后剩下一个数时,如果是奇数,则牛牛赢;否则牛妹赢。
  如果 n = 1 n=1 n=1,直接判断结果; n = 2 n=2 n=2时,两个数都是偶数时,牛妹赢,否则都是牛牛赢; n ≥ 3 n\ge 3 n3时,①如果偶数的个数多余1个,一定是牛妹赢:我们发现牛牛每次操作最多减少一个偶数,但牛妹可以减少两个奇数同时带来一个偶数,不管牛牛怎么操作,每一轮牛妹都选择两个奇数相加就可以维持偶数个数最少不改变,两个偶数朝上和一个奇数的局面一定是牛妹赢。②如果只有一个偶数,那就相当于牛妹先手。③奇数个奇数时,谁后手谁赢;④偶数个奇数时,谁先手谁赢。

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
const int maxn=1e6+100;
int a[maxn];
int main()
{
    
    
    close;int n,num_even=0,num_odd=0;cin>>n;
    for(int i=1;i<=n;++i)
        cin>>a[i],(a[i]&1)?num_odd++:num_even++;
    if(n==1){
    
    if(num_odd==1) cout<<"NiuNiu";else cout<<"NiuMei";}
    else if(n==2){
    
    if(num_even==2) cout<<"NiuMei";else cout<<"NiuNiu";}
    else{
    
    
    	if(num_even>=2) cout<<"NiuMei";
        else if(num_even==1){
    
    if(num_odd&1) cout<<"NiuNiu";else cout<<"NiuMei";}
        else{
    
    if(num_odd&1) cout<<"NiuMei";else cout<<"NiuNiu";}
    }
}

猜你喜欢

转载自blog.csdn.net/CUMT_William_Liu/article/details/113702599