acwing暑假训练题解(Week1&2)

Week1

最大异或和

 思路:我们求一个区间的异或值转换为求区间端点前缀异或值之间进行异或,先将以每个端点为右端点的满足题意的区间的前缀异或值加入tire树中,然后进行查询,但是需要注意的是我们在更新过程中需要对一些不满足题意的端点前缀异或值进行删除,这个和添加操作类似,注意学习一下如何在tire树中添加及删除元素

代码:

#include<iostream>
using namespace std;
const int N=100010*31,M=100010;
int idx,son[N][2],cnt[N];
int a[M],sum[M];
void insert(int x,int v)//v为1代表添加,为-1代表删除
{
    int p=0,t;
    for(int i=30;i>=0;i--)
    {
        t=x>>i&1;
        if(!son[p][t]) son[p][t]=++idx;
        p=son[p][t];
        cnt[p]+=v;
    } 
}

int query(int x)
{
    int p=0,t,ans=0;
    for(int i=30;i>=0;i--)
    {
        t=x>>i&1;
        if(cnt[son[p][!t]]) p=son[p][!t],ans+=1<<i;
        else p=son[p][t];
    }
    return ans;
}

int main()
{
    int ans=0;
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        {
            cin>>a[i];
            sum[i]=sum[i-1]^a[i];
        }
    insert(sum[0],1);
    for(int i=1;i<=n;i++)
    {
        if(i>m) insert(sum[i-m-1],-1);
        ans=max(ans,query(sum[i]));
        insert(sum[i],1);
    }
    printf("%d",ans);
    return 0;
}

最大的和

 思路:本来就已经可选的数我们计入答案即可,我们的目的是让尽可能大的不可选数变为可选数,所以我们可以对不可选数求前缀和,再求出长度为k的区间中的和的最大值加上原来可选数的和即可

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
const int N=100003;
int a[N];
long long sum[N];
int main()
{
    int n,k;
    cin>>n>>k;
    long long ans=0;
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    int t;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&t);
        if(t)
        {
            ans+=a[i];
            a[i]=0;
        }
        sum[i]=sum[i-1]+a[i];
    }
    long long maxx=0;
    for(int i=k;i<=n;i++)
        maxx=max(maxx,sum[i]-sum[i-k]);
    ans+=maxx;
    printf("%lld",ans);
    return 0;
}

序列最大收益

输入样例:

4 1 3
1 4 2
0 3 0 1
3 0 0 0
0 0 0 0
1 0 0 0

输出样例:

3

 dp[i][j]表示前i个数最多删除j个数获得的最大值,这道题最难想的就是这个dp数组表示含义了,知道了这个其他的就没什么难点了,详情见代码

#include<iostream>
#include<string.h>
#include<malloc.h>
using namespace std;
const int N=210;
int a[N],w[N][N],dp[N][N];
//dp[i][j]表示从前i个数中去掉不包含i的k个数的最大收益和 
int main()
{
    int n,k,m;
    cin>>n>>k>>m;
    for(int i=1;i<=m;i++) cin>>a[i];
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            cin>>w[i][j];
    int ans=0;
    for(int i=2;i<=m;i++)
    for(int j=0;j<=k;j++)//记录删除元素总个数 
    for(int v=1;v<i;v++)//k记录倒数第二个元素的位置 
        if(j>=i-v-1)
        {
            dp[i][j]=max(dp[i][j],dp[v][j-(i-v-1)]+w[a[v]][a[i]]);
            ans=max(ans,dp[i][j]);
        }
    printf("%d",ans);
    return 0;
}

不同路径数

 思路:这道题目就是先bfs存储所有可能产生的数,再对这些数进行去重,最后输出不重复的数的个数即可,可以直接用STL中unordered_set来实现

代码:

#include<iostream>
#include<algorithm>
#include<cstring> 
#include<unordered_set>
using namespace std;
int a[6][6],n,m,k;
unordered_set<int> S;//可以自动去重
int nx[4]={0,0,1,-1},ny[4]={1,-1,0,0};
void dfs(int now,int x,int y,int num)
{
    if(now>k)
    {
        S.insert(num);
        return ;
    }
    for(int i=0;i<4;i++)
    {
        int tx=x+nx[i],ty=y+ny[i];
        if(tx>=1&&tx<=n&&ty>=1&&ty<=m)
            dfs(now+1,tx,ty,num*10+a[tx][ty]);
    }
}
int main()
{
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
        cin>>a[i][j];
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
        dfs(1,i,j,a[i][j]);
    printf("%d",S.size());
    return 0; 
}

最长公共子序列

思路:需要注意的一点是元素中所有元素均不相同,这样我们就可以把第一个集合中的元素从小到大映射为1~n,再把第二个集合中的元素映射为1~n中的数,由于第一个序列映射后直接是单调递增序列,所以直接对第二个序列求最长单调上升子序列即可,但是需要注意的一点就是第一个序列中的元素并不一定完全与第二个序列中的元素相同,所以这个地方我们需要特殊处理一下,详情见代码,如果还有不知道如何nlogn求取最长上升子序列长度的小伙伴可以看下我之前的一篇博客,下面是博客地址:最长子序列问题_AC__dream的博客-CSDN博客

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
const int N=1000003;
int a[N],mp[N],ans[N];
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        mp[a[i]]=i;
    }
    int t,cnt=0;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&t);
        if(mp[t])  a[++cnt]=mp[t];//需要注意的是第一个序列中的元素并不一定完全与第二个序列中的元素相同 
    }
    memset(ans,0x3f,sizeof ans);
    for(int i=1;i<=cnt;i++)
        *lower_bound(ans+1,ans+n+1,a[i])=a[i];
    printf("%d",lower_bound(ans+1,ans+n+1,0x3f3f3f3f)-ans-1);
    return 0;
}

星期几

输入样例:

9 October 2001
14 October 2001

输出样例:

Tuesday
Sunday

这道题没有思维上的难度,就是一个直接模拟的题目,详情参考代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_map>

using namespace std;

int months[13] = {
    0,31,28,31,30,31,30,31,31,
    30,31,30,31
};

unordered_map<string,int> month_name={
    {"January",1},
    {"February",2},
    {"March",3},
    {"April",4},
    {"May",5},
    {"June",6},
    {"July",7},
    {"August",8},
    {"September",9},
    {"October",10},
    {"November",11},
    {"December",12},
};

string week_name[7] = {
    "Monday","Tuesday","Wednesday",
    "Thursday","Friday","Saturday",
    "Sunday"
};

int is_leap(int year)
{
    return year% 4==0&&year%100||year%400==0;
}

int get_days(int year, int month)
{
    int s=months[month];
    if (month==2) return s+is_leap(year);
    return s;
}

int main()
{
    int d,m,y;
    string str;
    while (cin>>d>>str>>y)
    {
        m=month_name[str];
        int i=1,j=1,k=1;
        int days=0;
        while (i<y||j<m||k<d)
        {
            k++,days++ ;
            if (k>get_days(i,j))
            {
                k=1;
                j++ ;
                if (j>12)
                {
                    j=1;
                    i++ ;
                }
            }
        }

        cout<<week_name[days%7]<<endl;
    }
    return 0;
}

阶乘的和

 思路:我们观察n的大小发现n小于10!,并且不可能出现n=i!+j!,且i和j相同的情况,那么我们只需要枚举从0~9这几个数中任意选出若干个数能够组成的数求出来,并判断其与询问的关系即可

代码:

#include<iostream>
#include<cstdio>
using namespace std;
int fact[10];
int a[10000000];
int main()
{
    fact[0]=1;
    int ans=0;
    for(int i=1;i<10;i++)
        fact[i]=fact[i-1]*i;
    for(int i=1;i<1<<9;i++)
        {
            ans=0;
            for(int j=0;j<9;j++)
                if(i>>j&1)
                    ans+=fact[j+1];
            a[ans]++;a[ans+1]++;
        }
    int n;
    while(1)
    {
        cin>>n;
        if(n<0) return 0;
        if(a[n]) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}

week2

最大面积

输入样例:

4 2
10
11
11
11
3
0 0
2 0
3 1

输出样例:

6
3
4

思路:仔细分析我们就可以发现,答案矩阵一定在所去除方格的上方或者下方或者左方或者右方,他一定可以被一个方向上的区间涵盖,画个图应该就知道为什么了
那我们现在就要预处理出来每一个方格四个方向上符合题意的最大的矩形面积
至于上方么,这个和第一道例题是类似的,处理下方时,我们应该将矩形旋转180度当作上方的矩形来处理,左方和右方的矩形就需要旋转90度,也当作朝上的矩形来看待;
说起来比较容易,可真正理解起来并没有那么简单,博主也是理解了好长时间才明白的,不过对于大家的思维提升真的有帮助,希望大家能够好好理解!
这道题目是单调栈的应用,我之前在博客中介绍到两道比较基础且和这道题目关联性较强的单调栈题目,如果对这道题目还不是很理解的话可以看下我之前的博客,下面附上博客地址:

单调栈及其应用_AC__dream的博客-CSDN博客

代码:

//直接调用stl库里面的栈 (有时会被卡时间) 
#include<iostream>
#include<cstdio>
#include<stack>
#include<cstring> 
using namespace std;
const int N=2002;
typedef pair<int,int> PII;
int U[N],D[N],L[N],R[N];
int l[N],r[N]; 
char a[N][N];
int s[N][N];
int n,m;
stack<PII> ans;
//calc函数求的是以以它为底边的最大矩形面积 
int calc(int h[],int n)//n为数组长度 
{
    while(!ans.empty()) ans.pop();
    for(int i=1;i<=n;i++)
    {
        while(!ans.empty()&&ans.top().first>=h[i]) ans.pop();
        if(ans.empty()) l[i]=0;
        else l[i]=ans.top().second;
        ans.push(make_pair(h[i],i));
    }
    while(!ans.empty()) ans.pop();
    for(int i=n;i>=1;i--)
    {
        while(!ans.empty()&&ans.top().first>=h[i]) ans.pop();
        if(ans.empty()) r[i]=n+1;
        else r[i]=ans.top().second;
        ans.push(make_pair(h[i],i));
    }
    int ans=0;
    for(int i=1;i<=n;i++)
        ans=max(ans,h[i]*(r[i]-l[i]-1));
    return ans;
}


void init()
{
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
            if(a[i][j]=='1') s[i][j]=s[i-1][j]+1;
            else s[i][j]=0;
        U[i]=max(U[i-1],calc(s[i],m));
    }
    memset(s,0,sizeof(s));
    for(int i=n;i>=1;i--)//以i为底边 
    {
        for(int j=1;j<=m;j++)
            if(a[i][j]=='1') s[i][j]=s[i+1][j]+1;
            else s[i][j]=0;
        D[i]=max(D[i+1],calc(s[i],m));
    }
    memset(s,0,sizeof(s));
    //求l和r数组时,以列作为矩形底边,将矩形旋转90度 
    for(int i=1;i<=m;i++)
    {
        for(int j=1;j<=n;j++)
            if(a[j][i]=='1') s[i][j]=s[i-1][j]+1;//这里写成s[i][j]的原因是直接将数组转置,将求第i列转换为求第i行 
            else s[i][j]=0;
        L[i]=max(L[i-1],calc(s[i],n));
    }
    memset(s,0,sizeof(s));
    for(int i=m;i>=1;i--)
    {
        for(int j=1;j<=n;j++)
            if(a[j][i]=='1') s[i][j]=s[i+1][j]+1;
            else s[i][j]=0;
        R[i]=max(R[i+1],calc(s[i],n));
    }
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) scanf("%s",a[i]+1);
    int x,y;
    init();
    int q;
    cin>>q; 
    while(q--)
    {
        cin>>x>>y;
        x++;y++;//注意数组下标 
        printf("%d\n",max(max(U[x-1],D[x+1]),max(L[y-1],R[y+1])));
    }
    return 0;
}


//数组模拟栈 
#include<iostream>
#include<cstdio>
#include<stack>
#include<cstring> 
using namespace std;
const int N=2002;
int U[N],D[N],L[N],R[N];
int l[N],r[N]; 
char a[N][N];
int s[N][N];
int n,m;
int ans[N],tt;
//calc函数求的是以以它为底边的最大矩形面积 
int calc(int h[],int n)//n为数组长度 
{
    tt=0;
    for(int i=1;i<=n;i++)
    {
        while(tt&&h[ans[tt]]>=h[i]) tt--;
        if(!tt) l[i]=0;
        else l[i]=ans[tt];
        ans[++tt]=i;
    }
    tt=0;
    for(int i=n;i>=1;i--)
    {
        while(tt&&h[ans[tt]]>=h[i]) tt--;
        if(!tt) r[i]=n+1;
        else r[i]=ans[tt];
        ans[++tt]=i;
    }
    int ans=0;
    for(int i=1;i<=n;i++)
        ans=max(ans,h[i]*(r[i]-l[i]-1));
    return ans;
}


void init()
{
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
            if(a[i][j]=='1') s[i][j]=s[i-1][j]+1;
            else s[i][j]=0;
        U[i]=max(U[i-1],calc(s[i],m));
    }
    memset(s,0,sizeof(s));
    for(int i=n;i>=1;i--)//以i为底边 
    {
        for(int j=1;j<=m;j++)
            if(a[i][j]=='1') s[i][j]=s[i+1][j]+1;
            else s[i][j]=0;
        D[i]=max(D[i+1],calc(s[i],m));
    }
    memset(s,0,sizeof(s));
    //求l和r数组时,以列作为矩形底边,将矩形旋转90度 
    for(int i=1;i<=m;i++)
    {
        for(int j=1;j<=n;j++)
            if(a[j][i]=='1') s[i][j]=s[i-1][j]+1;//这里写成s[i][j]的原因是直接将数组转置,将求第i列转换为求第i行 
            else s[i][j]=0;
        L[i]=max(L[i-1],calc(s[i],n));
    }
    memset(s,0,sizeof(s));
    for(int i=m;i>=1;i--)
    {
        for(int j=1;j<=n;j++)
            if(a[j][i]=='1') s[i][j]=s[i+1][j]+1;
            else s[i][j]=0;
        R[i]=max(R[i+1],calc(s[i],n));
    }
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) scanf("%s",a[i]+1);
    int x,y;
    init();
    int q;
    cin>>q; 
    while(q--)
    {
        cin>>x>>y;
        x++;y++;//注意数组下标 
        printf("%d\n",max(max(U[x-1],D[x+1]),max(L[y-1],R[y+1])));
    }
    return 0;
}

谁是你的潜在朋友

输入样例:

4 5
2
3
2
1

输出样例:

1
BeiJu
1
BeiJu

 思路:我们直接开辟两个个数组a[]和cnt[],a[i]记录第i个人喜欢的书的编号,而cnt[i]记录喜欢第i本书的人有多少个,直接访问就行

代码:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=202;
int a[N],cnt[N];
int main()
{
    int n,m;
    cin>>n>>m;
    int x;
    for(int i=1;i<=n;i++)
    {
        cin>>x;
        a[i]=x;
        cnt[x]++;
    }
    for(int i=1;i<=n;i++)
        if(cnt[a[i]]>1)
            printf("%d\n",cnt[a[i]]-1);
        else 
            printf("BeiJu\n");
    return 0;
}

2的幂次方

 思路:由于输出格式是递归给出的,所以直接对这个数进行递归输出,按照题目中给定的格式进行递归,细节见代码

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
string dfs(int n)
{
    string ans;
    for(int i=14;i>=0;i--)
    {
        if(n>>i&1)
        {
            if(ans.size()) ans+='+';
            if(!i) ans+="2(0)";
            else if(i==1) ans+='2';
            else ans+="2("+dfs(i)+")";
        }
    }
    return ans;
}
int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
        cout<<dfs(n)<<endl;
    return 0;
}

K-优字符串

输入样例:

2
5 1
ABCAA
4 2
ABAA

输出样例:

Case #1: 0
Case #2: 1

 思路:直接读入字符串,并求出优良分数,我们每次可以改变一个优良分数,所以我们的最少操作次数就是我们需要达到的优良分数减去我们当前的优良分数

代码:

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=200002;
char s[N];
int main()
{
    int T,t;
    cin>>T;
    t=T;
    int n,k;
    while(t--)
    {
        cin>>n>>k;
        scanf("%s",s+1);
        int cnt=0;
        for(int i=1;i<=n/2;i++)
            if(s[i]!=s[n-i+1])
                cnt++;
        printf("Case #%d: %d\n",T-t,abs(cnt-k));
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值