[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)
实现起来挺复杂的,也许是我对树状数组不熟悉,需要先离散化,再类似求逆序数一样维护两个树状数组,会爆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,树状数组,二分,离散化不熟悉,不能灵活运用,打的很惨,希望在今后的日子里,加油!