C BBuBBBlesort!
题意:
给出一个长度为N的数组A,数组中的元素两两不同,你可以进行两种操作:
1.交换两个相邻元素。
2. 交换两个中间间隔一个数字的元素,即:Ai-1 和 Ai+1 (如果Ai-1 和 Ai+1存在)
要求用这两种操作将A数组按升序排序,求操作1 至少要用多少次。
思路:
显然,仅使用操作2,可以将A中奇数位置和偶数位置的数分别排好序,因此,如果这个数排序后位置的奇偶性与排序前的位置的奇偶性不同,就必须使用操作1,而使用一次操作一会改变两个数的奇偶性。所以,统计排序前后奇偶性不同的数的个数,再除以2即是答案。
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 100010
int n,ans;
struct node
{
int x,id;
}a[MAXN];
bool cmp(node A,node B)
{
return A.x<B.x;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i].x);
a[i].id=i;
}
sort(a+1,a+1+n,cmp);
for(int i=1;i<=n;i++)
ans+=((a[i].id&1)!=(i&1));
printf("%d\n",ans/2);
}
D - Anticube
题意
有一个长度为N的数组S,要求从S中选择一些数,使得在你选择出的这些数中,任意两个数的乘积不是一个立方数。注意:S中的数可能有重复。
求最多能从S中选择多少个数满足要求。(
)
思路
两个数的乘积是否为立方数实际上只与将这两个数质因数分解后每个素数的指数模3后的数有关,因此我们将数
表示成
的形式,并求出
。
显然,
为一个立方数,对于能表示成
和能表示成
的两类数,我们只能选择其中一类,那么我们贪心地选择较多的那类。特别的,如果
,那么只能选择这类数中的一个。
一个实现上的细节:如果
分解
,时间复杂度太大,所以
分解
,最后剩下的数如果不是1,就只有两种情况,它为一个素数或两个不同的素数的积(指数为1),或它为一个素数的平方(指数为2),特判一下即可。
#include<cstdio>
#include<map>
#include<algorithm>
#include<cmath>
using namespace std;
#define MAXN 100000
#define LL long long
int tot,vis[MAXN],n,ans;
LL prim[MAXN],a[MAXN+5],b[MAXN+5];
map<LL,int>cnt;
void Pre()
{
for(int i=2;i<MAXN;i++)
{
if(!vis[i]) prim[tot++]=i;
for(int j=0;prim[j]*i<MAXN;j++)
{
vis[i*prim[j]]=1;
if(i%prim[j]==0) break;
}
}
}
void Solve(int i,LL x)
{
a[i]=b[i]=1;
for(int j=0;prim[j]*prim[j]*prim[j]<=x;j++)
{
int c=0;
while(x%prim[j]==0) x/=prim[j],c++;
if(c%3==1) a[i]*=prim[j],b[i]*=prim[j]*prim[j];
else if(c%3==2) a[i]*=prim[j]*prim[j],b[i]*=prim[j];
}
if(x!=1)
{
LL k=sqrt(x);
if(k*k==x) a[i]*=k*k,b[i]*=k;
else a[i]*=x,b[i]*=x*x;
}
}
int main()
{
Pre();
scanf("%d",&n);
LL x;
for(int i=1;i<=n;i++)
{
scanf("%lld",&x);
Solve(i,x);
cnt[a[i]]++;
}
for(int i=1;i<=n;i++)
{
if(cnt[a[i]]==0) continue;
if(a[i]==b[i]) ans++,cnt[a[i]]=0;
else ans+=max(cnt[a[i]],cnt[b[i]]),cnt[a[i]]=0,cnt[b[i]]=0;
}
printf("%d\n",ans);
}
E - Sequential operations on Sequence
题意
起初,有一个长度为N(N≤10^5)的数组A,Ai=i,(1≤i≤N),接下来,你要进行Q次操作,每次操作有两个步骤:
一:将A数组无限次放置在A’数组的末尾,得到了一个无限长的A’。
(如:A={1,2},得到A’={1,2,1,2,1,2,1,2,…} )
二:从A’中取前qi个数,作为新的A数组。qi≤10^18
(如:从A’中取前5个数,得到A={1,2,1,2,1})
求Q次操作后的A数组中,i(1≤i≤N)出现的次数。
思路
一个比较显然的结论:若
且
,那么
是不会对答案产生贡献的,因为
对最终数组有着更严格的限制。因此,可以从后往前扫一遍得到一个
的递减的序列,这个序列中的每一个数都会对答案产生影响。将这个递减序列记为st,反序后有st[1]<st[2]<…<st[r],其中st[r]即Q次操作后的A数组长度,是我们要求出的,我们已知的就只有st[1],st[1]≤n。
一种比较暴力的想法(在st[i]比较小的时候)是一个一个求出长为st[i]的数组,但针对这道题来说,时间和空间都不够。
考虑优化:st[i]可以转化为 c=st[i]/st[i-1] 个 st[i-1] 再加上长度为 r=st[i]%st[i-1] 的 st[i-1] 的前缀,而这个前缀可以在st中向前找到第一个小于等于它的位置并再次进行这样的转化,递归下去最终这个前缀的长度r会小于等于st[1],也就是说,在st[1]中从1 ~ r 的每个数都在st[i]中多出现了一次。这样考虑的一个好处是我们不需要真正求出长为st[r]的数组,而是将st[r]表示为C个完整的元素从1 ~ st[1]的数组与某些1 ~ r 前缀的形式。
感觉这个优化就像是对暴力的剪枝,剪去了需要不断重复st[1]的部分。
优化后空间复杂度O(n),时间复杂度O(n*(logn)
):对于每一个st[i],我们要递归的转化前缀,类似于辗转相除法,有一个log的复杂度,而每次递归中需要二分找到第一个小于等于该前缀的位置,又有一个log,所以时间复杂度为O(n*(logn)
)。
#include<cstdio>
#include<algorithm>
using namespace std;
#define MAXN 100010
#define LL long long
int n,Q,r;
LL q[MAXN],st[MAXN],a[MAXN],c[MAXN];
void Solve(LL x,LL y)
{
int p=upper_bound(st,st+1+r,x)-st-1;
if(!p) a[1]+=y,a[x+1]-=y;
else c[p]+=y*(x/st[p]),Solve(x%st[p],y);
}
int main()
{
scanf("%d%d",&n,&Q);
if(Q==0)
{
for(int i=1;i<=n;i++)
printf("1\n");
return 0;
}
for(int i=1;i<=Q;i++)
scanf("%lld",&q[i]);
st[++r]=q[Q];
q[0]=n;
for(int i=Q-1;i>=0;i--)
if(st[r]>q[i])
st[++r]=q[i];
for(int i=1;i<r-i+1;i++)
swap(st[i],st[r-i+1]);
c[r]=1;
for(int i=r;i>1;i--)
{
c[i-1]+=c[i]*(st[i]/st[i-1]);
Solve(st[i]%st[i-1],c[i]);
}
for(int i=1;i<=n;i++)
a[i]+=a[i-1];
for(int i=1;i<=st[1];i++)
a[i]+=c[1];
for(int i=1;i<=n;i++)
printf("%lld\n",a[i]);
}