2020级HAUT新生周赛(四)@张承树专场

A:会写脚本的月月鸟

解法1:

首先读了一下题目,嗷,水题,很快啊很快,有的同学啪的一下就来了一个暴力。然后就TLE了。
仔细分析一下这个序列长度 2 ∗ 1 0 5 2*10^5 2105,在看询问的次数 1 ∗ 1 0 5 1*10^5 1105,如果每次询问都是1-n,这样时间复杂度是不是 O ( n ∗ m ) = 2 ∗ 1 0 10 O(n*m)=2*10^{10} O(nm)=21010 ,看起来不可行。

所以我们想个办法来优化一下算法,每次询问区间 [ l − r ] [l-r] [lr]所有值得 与 运算。
这个与在二进制中有什么特性吗?
两个位都为1时,结果才为1
例如:10001&10100=10000

我们这样思考,如果把每个数看成由二进制组成得,每个数都小于1e9,也就是说对应得二进制位数最多只有31位,嗷,也就是说这个这个数就可以当成这个二进制每位对应十进制的大小之和。
比如说:110101对应的十进制大小就是
1 ∗ 2 0 + 0 ∗ 2 1 + 1 ∗ 2 2 + 0 ∗ 2 3 + 1 ∗ 2 4 + 1 ∗ 2 5 1*2^0+0*2^1+1*2^2+0*2^3+1*2^4+1*2^5 120+021+122+023+124+125

我们可以把这整个区间的每个数拆分成31份,对每位分别进行&运算后,最后把这31份的结果相加即可,那么问题就转换成如何快速的求出每一份的值是多少?
通过与运算我们可以知,假设这段区间每个数二进制的最低位 进行&运算,如果这段区间任意一个数二进制的最低位是0,那么算出来的最低位&的值即为0.

当我们分成31份进行计算时,对于二进制的某一位,序列是否出现过0,如果出现过0,则计算后这一位的结果是0。

到这里,题目就变成,对于某个区间,查询0的个数了。只不过要查找31次(因为有31位)。

O(1)时间复杂度查询的话,我们可以使用前缀和进行计算。
在这里插入图片描述

复杂度分析:
时间复杂度: O ( 31 ∗ ( n + m ) ) O(31*(n+m)) O(31(n+m))
空间复杂度: O ( 31 ∗ n ) O(31*n) O(31n)

/*Keep on going Never give up*/
#pragma GCC optimize(3,"Ofast","inline")
#include<stdio.h>
#include<math.h>
#include<string.h>
const int maxn =2e5+10;
typedef long long ll;

int dp[maxn][35];
int main(){
    
    
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
    
    
        int x;
        scanf("%d",&x);
        for(int j=0;j<31;j++){
    
    
            if(((x>>j)&1)==0) dp[i][j]=dp[i-1][j]+1;
            else dp[i][j]=dp[i-1][j];   //前缀预处理0的个数
        }
    }
    for(int i=0;i<m;i++){
    
    
        int x,y;
        scanf("%d%d",&x,&y);
        int ans=0;
        for(int j=0;j<31;j++){
    
    
            if(dp[y][j]-dp[x-1][j]==0)  //如果等于0,代表这段区间这一位没有0出现
            ans+=(1<<j);
        }
        printf("%d\n",ans);
    }
    return 0;
}

解法2:

线段树,树状数组暴力莽一波。(建议先学会简单解法)
不过多介绍了,有兴趣的同学可以了解一下。
在建树后,它可以让单次查询的复杂度降为 O ( l o g n ) O(logn) O(logn)

复杂度分析:
时间复杂度: O ( n + m ∗ l o g n ) ) O(n+m*logn)) O(n+mlogn))
空间复杂度: O ( 4 ∗ n ) O(4*n) O(4n)

线段树代码:

/*Keep on going Never give up*/
#pragma GCC optimize(3,"Ofast","inline")
#include<stdio.h>
#include<math.h>
#include<string.h>
const int maxn =2e5+10;
typedef long long ll;

int tree[maxn<<2];
int a[maxn];
int n,m;

void build(int node,int l,int r){
    
    
    if(l==r){
    
    
        tree[node]=a[l];
        return ;
    }
    int mid=(l+r)/2;
    if(l<=mid) build(node*2,l,mid);
    if(r>mid) build(node*2+1,mid+1,r);
    tree[node]=tree[node*2]&tree[node*2+1];
}

int query(int node,int start,int ends,int l,int r){
    
    
    if(start>=l&&ends<=r){
    
    
        return tree[node];
    }
    int mid=(start+ends)/2;
    int ans=-1;
    if(l<=mid)  ans=ans&query(node*2,start,mid,l,r);
    if(r>mid)   ans=ans&query(node*2+1,mid+1,ends,l,r);
    return ans;

}

int main(){
    
    
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
    
    
        scanf("%d",&a[i]);
    }
    build(1,1,n);
    for(int i=0;i<m;i++){
    
    
        int l,r;
        scanf("%d%d",&l,&r);
        printf("%d\n",query(1,1,n,l,r));
    }
    return 0;

}

B:别看了 这是水题

题解:
这个题目算是一个构造题,按照题目要求构造出一个序列。
解法肯定会有很多种,当然不同解法输出的序列也不一样,这里介绍一种解法。

首先判断它是否能构成这样的序列。
如果 k ∗ 2 < = n k*2<=n k2<=n,那么可以构成,反之输出-1.(别问我为什么)

那么我们接下来就需要找一个通用的顺序来输出一下这个序列了。

解法1: 将序列拦腰斩断,分成两半,左边输出一个值,右边输出一个值(重复操作),以保证间隔大于等于k。

复杂度分析:
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)

解法1代码:

/*Keep on going Never give up*/
#pragma GCC optimize(3,"Ofast","inline")
#include<stdio.h>
#include<math.h>
#include<string.h>
const int maxn =2e5+10;
typedef long long ll;

int main(){
    
    
    int t;
    scanf("%d",&t);
    while(t--){
    
    
        int n,k;
        scanf("%d%d",&n,&k);
        if(n==1){
    
       //特判长度1的序列
            printf("1\n");
        }
        else if(k*2<=n){
    
    
            int temp=n/2;
            if(n%2==1){
    
       //长度为奇数时
                for(int i=2;i<=temp+1;i++){
    
    
                    printf("%d %d ",i+temp,i);
                }
                printf("1\n");
            }
            else{
    
       //长度为偶数时
                for(int i=1;i<=temp;i++){
    
    
                    printf("%d %d ",i+temp,i);
                }
                printf("\n");
            }
        }
        else printf("-1\n");
    }
    return 0;

}

代码2:

许金龙大神’s code:

#include<bits/stdc++.h>

using namespace std;

int main() {
    
    
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T; cin >> T;
    while(T --) {
    
    
        int n, k; cin >> n >> k;
        if (n == 1) cout << "1\n";
        else if (n < 2*k) cout << "-1\n";
        else {
    
    
            for(int i = k; i >= 1; -- i) {
    
    
                for(int j = i; j <= n; j += k) {
    
    
                    cout << j << " ";
                }
            }
            cout << "\n";
        }
    }
    return 0;
}

C:ACM脱单大法

题解:
这个题目的解法也有很多,因为复杂度卡的不是很严格,时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)应该都可以
比如说二分+暴力,后缀处理等。。

解法:
在这里插入图片描述

简单的小贪心做一下(算半个贪心。
首先,如果有解,那么前半部分和后半部分的最大最小值肯定跟整个序列最大最小值是相同的。
这样我们先遍历一遍序列找出最大值和最小值,前半段区间最大最小值和后半段区间最大最小值肯定跟整个序列的相同。
我们遍历一边序列记录下来第一个出现imax的位置maxpos和imin的位置minpos。
x = m a x ( m a x p o s , m i n p o s ) x=max(maxpos,minpos) x=max(maxposminpos)然后从序列x+1这个位置遍历序列,如果存在值等于最大值和存在值等于最小值,那么x就是答案,反之则输出-1。

复杂度分析:
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)
代码:

/*Keep on going Never give up*/
#pragma GCC optimize(3,"Ofast","inline")
#include<stdio.h>
#include<math.h>
#include<string.h>
#define max(a,b) ((a) > (b) ? (a) : (b))
#define min(a,b) ((a) < (b) ? (a) : (b))
const int maxn =1e5+10;
typedef long long ll;

int a[maxn];

signed main(){
    
    
//    freopen("in.txt","r",stdin);
//    freopen("out.txt","w",stdout);
    int t;
    scanf("%d",&t);
    while(t--){
    
    
        int n;
        scanf("%d",&n);
        int imax=-2e9-10,imin=2e9+10;
        int maxpos,minpos;
        for(int i=1;i<=n;i++){
    
    
            scanf("%d",&a[i]);
            if(imax<a[i]) imax=a[i],maxpos=i;
            if(imin>a[i]) imin=a[i],minpos=i;
        }
        int st=max(maxpos,minpos);
        bool xx=false,yy=false;
        for(int i=st+1;i<=n;i++){
    
    
            if(imax==a[i]) xx=true;
            if(imin==a[i]) yy=true;
        }
        if(xx&&yy) printf("%d\n",st);
        else printf("-1\n");
    }
}

D:Love_Jacques学长的游戏思维

坑点1:
在这里插入图片描述
实数都有小数点吗?

坑点2: 1.23与1.2300000

题解: 因为实数小于10,所以从左边往右边比较即可,注意一下不同长度字符串的处理,可以把较短的字符串后面全补为0。

复杂度分析:
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)
代码:

/*Keep on going Never give up*/
#pragma GCC optimize(3,"Ofast","inline")
#include<stdio.h>
#include<math.h>
#include<string.h>
#define max(a,b) ((a) > (b) ? (a) : (b))
#define min(a,b) ((a) < (b) ? (a) : (b))
const int maxn =1e5+10;
typedef long long ll;

char s[1100],s1[1100];

signed main(){
    
    
//    freopen("in.txt","r",stdin);
//    freopen("out.txt","w",stdout);
    int t;
    scanf("%d",&t);  //x的值  分别代表谁大谁小
    while(t--){
    
    
        scanf("%s",s);
        scanf("%s",s1);
        int lens=strlen(s);
        int lens1=strlen(s1);
        if(lens==1) s[1]='.',s[2]='\0',lens++;   //判断给出的是个整数还是实数,如果是实数则补全
        if(lens1==1) s1[1]='.',s1[2]='\0',lens1++;
        if(lens<lens1) for(int i=lens;i<lens1;i++) s[i]='0';  //补0
        else for(int i=lens1;i<lens;i++) s1[i]='0';
        int x=0;
        for(int i=0;i<max(lens1,lens);i++){
    
    
            if(s[i]>s1[i]){
    
       //判断大小,别忘记break
                x=1;
                break;
            }
            else if(s[i]<s1[i]){
    
    
                x=2;
                break;
            }
        }
        if(x==0) printf("EASY GAME!\n");
        else if(x==1) printf("AZNB\n");
        else printf("YZNB\n");
    }
}


E:后缀自动机next指针dag图上跑SG函数

题解:
贪心
考虑并且购买第i个宝石的价格是 i ∗ a [ i ] 。 i*a[i]。 ia[i]
那么我们不断拿第一个宝石即可。
但是宝石的价格可能是负数,所以遇到价格位负数的宝石(倒贴你钱),我们如何最大化他的值呢,题目可知,想让他倒贴给你钱最多,也就是这个宝石初始位置下标乘a[i]即可。
也可以理解为,先从后往前把小于零的宝石买掉,然后在第一个位置一直买即可。

复杂度分析:
时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( 1 ) O(1) O(1)

代码:

/*Keep on going Never give up*/
#pragma GCC optimize(3,"Ofast","inline")
#include<stdio.h>
#include<math.h>
#include<string.h>
#define max(a,b) ((a) > (b) ? (a) : (b))
#define min(a,b) ((a) < (b) ? (a) : (b))
const int maxn =1e5+10;
typedef long long ll;

signed main(){
    
    
//    freopen("in.txt","r",stdin);
//    freopen("out.txt","w",stdout);
    int n;
    scanf("%d",&n);
    ll ans=0;
    for(int i=1;i<=n;i++){
    
    
        ll x;
        scanf("%lld",&x);
        if(x<0) ans+=x*i;
        else ans+=x;
    }
    printf("%lld",ans);
    return 0;
}

F:新建 Microsoft PowerPoint 演示文稿.pptx

题解:
暴力即可,设一个数组记录所有出现过的数有哪些,每次从1开始找最小没有出现过的数是什么。

复杂度分析:
时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( n ) O(n) O(n)

代码:

/*Keep on going Never give up*/
//#pragma GCC optimize(3,"Ofast","inline")
#include <stdio.h>
typedef long long ll;
const int mod = 1e9+7;
int a[1000+10];
int se(){
    
       //查找未出现过的最小下标
    for(int i=1;i<=1000;i++){
    
    
        if(!a[i]) return i;
    }
    return -1;
}

int main(){
    
    
    int n;
    scanf("%d",&n);
    for(int i=0;i<1000;i++) a[i]=0;  //可写可不写(全局变量初始值为0,可以不写
    while(n--){
    
    
        int opt,x;
        scanf("%d",&opt);
        if(opt==1){
    
    
            int pos=se();
            a[pos]=1;   //标记该结点出现过,为后面删除提供遍历
            if(pos==1) printf("新建 Microsoft PowerPoint 演示文稿.pptx\n");
            else printf("新建 Microsoft PowerPoint 演示文稿(%d).pptx\n",pos);
        }
        else{
    
    
            scanf("%d",&x);
            if(a[x]){
    
       //查询该结点是否出现过。
                printf("Successful\n");
                a[x]=0;
            }
            else printf("Failed\n");
        }
    }
    return 0;
}

ps:这个题也有更优的解法,用优先队列即可,学有余力的同学可以尝试一下,思想就是是,一开始把所有未出现的文档全都塞到优先队列中(小顶锥),每次取优先队列头,然后删除优先队列头节点,如果遇到删除改结点,如果这个结点存在,再把这个结点塞进优先队列即可。

复杂度分析:
时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
空间复杂度: O ( n ) O(n) O(n)

代码:

/*Keep on going Never give up*/
#pragma GCC optimize(3,"Ofast","inline")
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<bits/stdc++.h>
const int maxn =2e5+10;
typedef long long ll;
using namespace std;
int visited[maxn];
signed main(){
    
    
//    freopen("27.in","r",stdin);
//    freopen("27.out","w",stdout);
    
    int n;
    cin>>n;
    
    memset(visited,false,sizeof visited);
    priority_queue<int,vector<int>,greater<int>> q;
    for(int i=1;i<=n;i++){
    
       //未出现的全部塞进优先队列
        q.push(i);
    }
    for(int i=0;i<n;i++){
    
    
        int opt,x;
        scanf("%d",&opt);
        if(opt==1){
    
    
            int t=q.top();   //每次取最小元素
            q.pop();
            visited[t]=true;
            if(t==1) printf("新建 Microsoft PowerPoint 演示文稿.pptx\n");
            else{
    
    
                printf("新建 Microsoft PowerPoint 演示文稿(%d).pptx\n",t);
            }
        }
        else{
    
    
            scanf("%d",&x);
            if(visited[x]){
    
    
                printf("Successful\n");
                visited[x]=false;
                q.push(x);   //删除了改元素,把元素重新放回优先队列。
            }
            else printf("Failed\n");
        }
    }
    return 0;
}

G:禁止复读

题解:
用一下c++的sort是我最后写c语言报告的倔强了,不会的同学学一下嘛,真的很好用QAQ。

这个只要搞清楚如何判断一个人是否发起复读是最重要的事情。
假设你要判断第i个人是否为复读发起者
那么你就要检查一下第i-1个人说的话是不是跟他一样
并且第i-2个热播说的话跟他不一样
如果这两者都符合,那么这个人就是复读发起者。

还有一点就是去重,可能一个人会复读好几次,但是你只需要输出一次他的序列即可。
你可以设置一个数组记录已经被记录下来的人,也可以最后对序列进行去重即可。

复杂度分析:
时间复杂度: O ( ∣ s ∣ ∗ n ) O(|s|*n) O(sn)
空间复杂度: O ( ∣ s ∣ ∗ n ) O(|s|*n) O(sn)

代码:

/*Keep on going Never give up*/
//#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>

char s[1010][55];
int visited[1010];

int ans[1010];

int judge(char a[],char b[]){
    
    
    int lena=strlen(a);
    int lenb=strlen(b);
    if(lena!=lenb) return 0;
    for(int i=0;i<lena;i++) if(a[i]!=b[i]) return 0;
    return 1;
}

int main(){
    
    
//    freopen("1.in","r",stdin);
//    freopen("1.out","w",stdout);
    int n,cnt=0;
    scanf("%d",&n);
    for(int i=2;i<=n+1;i++){
    
    
        int opt;
        scanf("%d",&opt);
        scanf("%s",s[i]);
        if(judge(s[i],s[i-1])&&!judge(s[i],s[i-2])){
    
    
            if(!visited[opt]){
    
    
                visited[opt]=1;
                ans[cnt++]=opt;
            }
        }
    }
    std::sort(ans,ans+cnt);
    printf("%d\n",cnt);
    for(int i=0;i<cnt;i++) printf("%d ",ans[i]);
    printf("\n");

}

H:吓得我赶紧出了个签到题

那串代码得意思:
其实是个splay,平衡树的一种,花里胡哨的一顿乱操作其实没啥用处。
为什么会输出lcltql!是因为对这个数每次添加一个结点(正整数),然后对树进行求和操作,一共求和了七次(当然也添加了七次,每添加一次求和一次),让着七次的值正好等于每个字母对应的ASCII码即可。

/*Keep on going Never give up*/
//#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define int long long
#define fin(filename) freopen(filename,"r",stdin)
#define fout(filename) freopen(filename,"w",stdout)
#define endl '\n'
#define Accepted 0
#define AK main()
#define I_can signed
using namespace std;
const int maxn =2e5+10;
const int MaxN = 1e9+7;
const int MinN = -1e9+7;
typedef long long ll;
const int inf=0x3f3f3f3f;
const ll mod=1e8;
const int N = 5e6 + 100;
 
 
 
signed main(){
    
    
//    freopen("1.in","r",stdin);
//    freopen("1.out","w",stdout);
//    ios::sync_with_stdio(false);
//    cin.tie(0);
//    cout.tie(0);
    cout<<"ACDA"<<endl;
    cout<<"lcltql!"<<endl;
}
 

猜你喜欢

转载自blog.csdn.net/xxxxxiao123/article/details/111086158