题解 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
",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/ALANALLEN21LOVE28/p/12673564.html