题解 P6306 【「Wdsr-1」小铃的书】
水题解来了
开始讲题
一步一步来吧,先是\(50pt\)
\(first--50pt\)
话说这个部分应该都会吧
其实这个部分就是把所有能想到的做法都拿出来爆搞就是了,比如存储后排序再扫描处理,或者直接用桶存
而我们现在关心的是后一种做法。为什么?我也不知道,因为这与我们之后的\(75pt\)以及正解的思路都密切相关。而\(50pt\)部分的桶做法应该都能想到吧,就是用个\(map\)暴力存储,再看最后的出现次数是否\(mod~k\)为零
\(second--75pt\)
可是\(map\)到了第二个部分\(75pt\)时,\(8mb\)会卡得你不要不要的,所以就只能考虑其它的奇技淫巧了
依旧考虑桶的思路,而目标是优化空间,那么就考虑将一个数拆成其\(k\)进制的若干部分,比如\(k=3\)时,将\((5)_ {10}\)变成\((12)_ {3}\),而将\((12)_ {3}\)的个位\(2\)存入专门存个位的桶中,十位\(1\)存入专门存十位的桶中,这个存其实就是将数加入桶中。而将桶取模\(k\),若有剩余那么剩下的必然是可以计入答案中的,关于这个我们假设混入的魔导书为\(t\),其存入的某一位设为第\(p\)位,则其存入的数值是\(ans_p=\frac{t\% k^{p+1}}{k^p}\),而显然\(k^p<t\%k^{p+1}<k^{p+1}\),所以\(0<ans_p<\frac{k^{p+1}}{k^p}=k\)(以上除号均是下取整),所以膜\(k\)后剩下的值一定是答案中的,可以加入
也就是以下代码:
fac[0]=1;
for(register int i=1;fac[i-1]<=lim;up=i++)
fac[i]=fac[i-1]*k;
for(register ll x;n;--n)
{
x=read();
for(register int i=up;x;--i)
if(x>=fac[i])
{
ans[i]+=x/fac[i];
if(ans[i]>=k)
ans[i]-=k;
x%=fac[i];
}
}
for(register int i=up;~i;--i)
res+=ans[i]*fac[i];
只给出了一部分,其中\(fac_i=k^i\),而\(res\)就是最后答案
\(third--100pt\)
而第二部分的空间\(O(log_2a_{max})\)是合格了,但显然时间\(O(nlog_ka_{max})\)被卡掉了,所以考虑进一步优化
继续考虑桶与拆分的思路,可以想到把\(second\)中的空间稍微开大一点以达到节约时间的目的
将一个数分成若干个部分,但这次不是统计那个部分的代数和了,而是统计某个数的该部分的出现次数,这样就可以成功解决问题了
比如将一个数\(a\)分为\(a_{1-8}\)的\(8\)个部分,其中\(2^{8* (i-1)}<=a_i<2^{8* i}\),那么再建立\(8\)个数组\(ans_{i,j}\),其中\(ans_{i,a_i}\)自加,那么这些数组就跟一个个小桶一样了,时间也节约了很多,只是\(O(n)\)套了一个\(8\)的常数,而能顺利解决问题
至于开\(8\)个数组只是怕\(MLE\),其实应该可以开更小的,但\(8\)个已经够了,不会\(T\)
短小精悍的代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
inline ll read()
{
ll x=0,ch=getchar();
for(;ch<'0'||ch>'9';ch=getchar());
for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
return x;
}
const int digit=1<<8,p=8;
int a[p][digit];
ll ans,n,k;
int main()
{
n=read(),k=read();
for(register ll x;n;--n)
{
x=read();
for(register int i=0;i<p;++i)
++a[i][(x>>(i*p))&(digit-1)];
}
for(register ll i=1;i<digit;++i)
for(register int j=0;j<p;++j)
if(a[j][i]%k)
ans+=(i<<(j*p));
printf("%lld\n",ans);
return 0;
}

浙公网安备 33010602011771号