[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,树状数组,二分,离散化不熟悉,不能灵活运用,打的很惨,希望在今后的日子里,加油!

posted @ 2018-01-29 17:41  遗风忘语  阅读(206)  评论(0编辑  收藏  举报