[Offer收割]编程练习赛46

[Offer收割]编程练习赛46赛后题解

A.AEIOU

分析

???与ou分开计算
把aei看作123,就转化成了最长不下降子序列问题
aeiouaeiou
最长不下降子序列问题有O(NlogN)的一般解法
本题比较特殊,只有aei三种字母,转移是O(1)的
a前面的字母一定是之前最后一个a
e前面的字母一定是之前1)最后一个a, 2)最后一个e
i前面的字母一定是之前1)最后一个a, 2)最后一个e, 3)最后一个i
?(N)

这题只要设5个变量代表最后一个a,e,i,o,u的位置,依次更新即可

#include <bits/stdc++.h>
using namespace std;

#define ll long long
const int N = 1e6+1e3;
char s[N];
int n;
int cnt1,cnt2,cnt3;

int main(int argc, char const *argv[])
{
    //freopen("offer_pratice_46_out.out","w",stdout);
    scanf("%s",s);
    n=strlen(s);
    int ans=0;
    cnt1=cnt2=cnt3=0;
    if(s[0]=='a') cnt1=1;
    if(s[0]=='e') cnt2=1;
    if(s[0]=='i') cnt3=1;
    for(int i=1;i<n;++i)
    {
        if(s[i]=='a') ++cnt1;
        if(s[i]=='e') cnt2=max(cnt1,cnt2)+1;
        if(s[i]=='i') cnt3=max(cnt1,max(cnt2,cnt3))+1;
    }
    ans=max(cnt1,max(cnt2,cnt3));
    cnt1=cnt2=0;
    if(s[0]=='o') cnt1=1;
    if(s[0]=='u') cnt2=1;
    for(int i=1;i<n;++i)
    {
        if(s[i]=='o') ++cnt1;
        if(s[i]=='u') cnt2=max(cnt1,cnt2)+1;
    }
    ans+=max(cnt1,cnt2);
    printf("%d\n",ans );
    return 0;
}

B.数字游戏

分析

交换位置和加减1是不相关的
可以认为先交换再加减
一旦交换方案确定了,最少加减操作可以直接按位计算
交换最终状态有N!种
对于每种状态,需要的最少交换次数可以计算
这是个经典问题
O(N*N!)

这题是一道离散化+计算最少交换次数+计算最少加减次数的题目,难点在于如何计算最少的交换次数,其实质为计算一个状态的环数目,举个例子,1133->3311,令1133->1234,那么3311变成3412,再遍历一遍即可求得环数,具体见代码

#include <bits/stdc++.h>
using namespace std;

int n;
int A[12],B[12],C[12];
int mp[12];
queue<int>q[11];
inline int work()//A->C的最少步数
{
    int cnt=0 ,AA[12],CC[12];
    for(int i=1;i<=n;++i) if(A[i]!=C[i])
    {
        AA[++cnt]=A[i];
        CC[cnt]=C[i];
    }
    /*
    memset(mp,0,sizeof(mp));
    int ret=0;
    for(int i=1;i<=cnt; ++i) if(mp[AA[i]]==0) mp[AA[i]]=(++ret);
    for(int i=1;i<=cnt;++i) AA[i]=mp[AA[i]],CC[i]=mp[CC[i]];
    int num=0,vis[12];
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=cnt;++i) if(!vis[i])
    {
        int j=i;
        num++;
        while(!vis[j])
        {
            vis[j]=1;
            j=CC[AA[j]];
        } 
    }
    */
    for(int i=1;i<=cnt;++i) while(!q[i].empty()) q[i].pop();
    int num=0,b;
    for(int i=1;i<=cnt;++i) q[AA[i]].push(i),AA[i]=i;
    for(int i=1;i<=cnt;++i)
    {
        b=CC[i];
        CC[i]=q[b].front();q[b].pop();
    }
    int vis[12];memset(vis,0,sizeof(vis));
    for(int i=1;i<=cnt;++i) if(!vis[i])
    {
        num++;
        while(!vis[i]) { vis[i]=1;i=CC[i]; }
    }
    return cnt-num;
}

int main()
{
    scanf("%d",&n);getchar();
    char s1[12],s2[12];
    scanf("%s %s",s1,s2);
    for(int i=0;i<n;++i) C[i+1]=A[i+1]=s1[i]-'0';
    for(int i=0;i<n;++i) B[i+1]=s2[i]-'0';
    int cnt=1;
    int ans=1e9;
    for(int i=2;i<=n;++i) cnt*=i;
    for(int i=1;i<=cnt;++i)//C->B
    {
        int ret=work(),x,y;
        for(int i=1;i<=n;++i)
        {
            x=B[i],y=C[i];
            if(x<y) swap(x,y);
            ret+=min(x-y,y+10-x);
        }
        ans=min(ans,ret);
        next_permutation(C+1,C+1+n);
    }
    printf("%d\n",ans);
    return 0;
}

C.第K小分数

分析

二分X∈[0, 1]
复杂度O(log(∑Pi)*N),适合N较小,Pi较大的情况

每次得到小于等于mid的数的个数为∑(mid*p[i])(向下取整),若刚好为K那么遍历一遍找出最接近mid的数即可

#include <bits/stdc++.h>
using namespace std;

int n;
int A[12],B[12],C[12];
int mp[12];
queue<int>q[11];
inline int work()//A->C的最少步数
{
    int cnt=0 ,AA[12],CC[12];
    for(int i=1;i<=n;++i) if(A[i]!=C[i])
    {
        AA[++cnt]=A[i];
        CC[cnt]=C[i];
    }
    /*
    memset(mp,0,sizeof(mp));
    int ret=0;
    for(int i=1;i<=cnt; ++i) if(mp[AA[i]]==0) mp[AA[i]]=(++ret);
    for(int i=1;i<=cnt;++i) AA[i]=mp[AA[i]],CC[i]=mp[CC[i]];
    int num=0,vis[12];
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=cnt;++i) if(!vis[i])
    {
        int j=i;
        num++;
        while(!vis[j])
        {
            vis[j]=1;
            j=CC[AA[j]];
        } 
    }
    */
    for(int i=1;i<=cnt;++i) while(!q[i].empty()) q[i].pop();
    int num=0,b;
    for(int i=1;i<=cnt;++i) q[AA[i]].push(i),AA[i]=i;
    for(int i=1;i<=cnt;++i)
    {
        b=CC[i];
        CC[i]=q[b].front();q[b].pop();
    }
    int vis[12];memset(vis,0,sizeof(vis));
    for(int i=1;i<=cnt;++i) if(!vis[i])
    {
        num++;
        while(!vis[i]) { vis[i]=1;i=CC[i]; }
    }
    return cnt-num;
}

int main()
{
    scanf("%d",&n);getchar();
    char s1[12],s2[12];
    scanf("%s %s",s1,s2);
    for(int i=0;i<n;++i) C[i+1]=A[i+1]=s1[i]-'0';
    for(int i=0;i<n;++i) B[i+1]=s2[i]-'0';
    int cnt=1;
    int ans=1e9;
    for(int i=2;i<=n;++i) cnt*=i;
    for(int i=1;i<=cnt;++i)//C->B
    {
        int ret=work(),x,y;
        for(int i=1;i<=n;++i)
        {
            x=B[i],y=C[i];
            if(x<y) swap(x,y);
            ret+=min(x-y,y+10-x);
        }
        ans=min(ans,ret);
        next_permutation(C+1,C+1+n);
    }
    printf("%d\n",ans);
    return 0;
}

D.逆序异或和

分析

按位计算
维护两个集合S0 = {Ai | Ai.bit[k]为0}, S1={Ai | Ai.bit[k]为1}
若Aj.bit[k]为0,则要求S1中大于Aj的元素有几个
若Aj.bit[k]位1,则要求S0中大于Aj的元素有几个e
可以用一维树状数组解决
O(NlogNlogAi)
\[\sum_{i<j,A_i>A_j}A_ixorA_j=\sum_{i<j,A_i>A_j}\sum_{k=0}^{32}A_i.bit[k]xorA_j.bit[k]*2^k=\sum_{k=0}^322^k*\sum_{i<j,A_i>A_j}A_i.bit[k]xorA_j.bit[k]\]

实现起来挺复杂的,也许是我对树状数组不熟悉,需要先离散化,再类似求逆序数一样维护两个树状数组,会爆int,具体见代码

#include <bits/stdc++.h>
using namespace std;

#define ll long long

int n,x;
int A[100100],B[100100];
int S1[100100][40],S2[100100][40];//S1[i][k]记录x.bit[k]为0,else为1

int lowbit(int x){ return x&(-x); }

void add1(int x,int k)
{
    for(int i=x;i<=n;i+=lowbit(i)) S1[i][k]++;
}
void add2(int x,int k)
{
    for(int i=x;i<=n;i+=lowbit(i))  S2[i][k]++;
}
int getsum1(int x,int k)
{
    int sum=0;
    for(int i=x;i>0;i-=lowbit(i)) sum+=S1[i][k];
    return sum;
}
int getsum2(int x,int k)
{
    int sum=0;
    for(int i=x;i>0;i-=lowbit(i)) sum+=S2[i][k];
    return sum;
}

int main(int argc, char const *argv[])
{
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
    {
        scanf("%d",&x);
        A[i]=B[i]=x;
    }
    sort(B+1,B+1+n);
    int K=0,y=B[n];
    while(y) K++,y>>=1;
    for(int i=1;i<=n;++i)
    {
        A[i]=lower_bound(B+1,B+1+n,A[i])-B;
    }
    ll ans=0;
    int num1[40],num2[40];
    memset(num1,0,sizeof(num1));
    memset(num2,0,sizeof(num2));
    for(int i=1;i<=n;++i)
    {
        x=B[A[i]];
        for(int k=0;k<K;++k)
        {
            //for(int p=1;p<=n;++p){for(int q=0;q<K;++q) printf("S1[%d][%d]=%d ",p,q,S1[p][q] );puts("");}
            int ret=0;
            if((1<<k)&x)
            {
                num2[k]++;
                add2(A[i],k);
                ret+=num1[k]-getsum1(A[i],k);
                //printf("ret1=%d\n",ret );
            }
            else
            {
                num1[k]++;
                add1(A[i],k);
                ret+=num2[k]-getsum2(A[i],k);
                //printf("ret2=%d\n",ret );
            }
            
            ans+=(1<<k)*1LL*ret;
        }
    }
    printf("%lld\n",ans );
    return 0;
}

/*
int main()
{
    printf("%d\n",(1^2)+(1^3)+(2^3) );
}*/

写在最后

这是我退役后第一场ACM比赛,比赛中对于LIS,树状数组,二分,离散化不熟悉,不能灵活运用,打的很惨,希望在今后的日子里,加油!

转载于:https://www.cnblogs.com/chendl111/p/8378566.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值