题意:
历经千辛万苦,pty终于打开了金字塔的锁。稍稍适应了外面刺眼的光线,pty抬头望去,眼前竟是一条不见尽头的狭长通道。这时候背后响起了奇怪的窸窣声,原来是金字塔内绿眼黑身的怪物追了过来。Pty来不及多想,便拼命往前奔去。通道狭窄又曲折,时不时还有断裂,不过Pty凭借TempleRun练成的娴熟技巧轻松通过。眼看着离怪物们越来越远时,一棵参天大树突然耸立在了道路中央,大树摆了摆身子,用苍老的声音说道:“孩子,我是远古的守护神。你打扰了这里的清净,想要从我这里通过,必须要解决一道来自远古的问题。”
现在有n堆石子,每堆石子分别有ai个,问有多少个d使得下式成立:
守护神出的题自然是神题了,同是身为神的你可以解决么?
数据范围:
对于30%的测试点,
对于60%的测试点,
对于100%的测试点,
Analysis:
这种题肯定是拆位统计了。需要每一个二进制位上有偶数个1。那么DP,但是如果减去 可能出现退位,非常不好处理。然后考虑设出退位状态,这时候发现如果我们从高位往低位DP。我们需要关注退了多少个1,但如果我们从低位到高位DP,根据当前退位状态,可以推出之后的状态,并不需要关心前面的状态。所以设 表示从低到高做到第 位,退位状态为 。但这样状态数,因为我们需要知道的是有多少个数退位了,若有 个数退位,则肯定是按前 个二进制位排序前 小。因为越小才会退位,那么状态就会少,然后就可以转移了,排序用基数排序,就可以做到 。注意到我们DP出来的是 的方案,等于 的时候要特判。
转移方程:
一开始我这里是最不理解的地方。
假如当前退位了
个,因为
是确定下来的,考虑
这一位选
还是选
去转移。
根据当前枚举到的位
排好序后统计出来的信息,我们可以根据是否能够向
位借位讨论来转移。
Code:
# include<cstdio>
# include<cstring>
# include<algorithm>
using namespace std;
const int N = 2e5 + 5;
typedef long long ll;
ll f[70][N],a[N];
int s[N],t[N],p[N],b[N];
int n;
int main()
{
scanf("%d",&n);
f[0][0] = 1;
for (int i = 1 ; i <= n ; ++i) scanf("%lld",&a[i]),p[i] = i;
for (int i = 0 ; i < 62 ; ++i)
{
for (int j = 1 ; j <= n ; ++j) t[j] = s[j] = (a[p[j]] & (1ll << i)) ? 1 : 0;
int h = 0;
for (int j = 1 ; j <= n ; ++j) if (!t[j]) b[++h] = p[j];
for (int j = 1 ; j <= n ; ++j) if (t[j]) b[++h] = p[j];
for (int j = 1 ; j <= n ; ++j) s[j] += s[j - 1],p[j] = b[j];
for (int j = 0 ; j <= n ; ++j)
if (f[i][j])
{
if ((s[n] + j - s[j] * 2) % 2 == 0) f[i + 1][j - s[j]] += f[i][j];
if ((2 * s[j] - j + n - s[n]) % 2 == 0) f[i + 1][n - s[n] + s[j]] += f[i][j];
}
}
ll all = 0;
for (int i = 2 ; i <= n ; ++i) all ^= a[p[i]] - a[p[1]];
if (!all) --f[62][0];
printf("%lld\n",f[62][0]);
return 0;
}