HDU 5798 Stabilization

题目简述:

  给定一个正整数序列(序列长度$n \le 10^5$)数字大小 $ < 2 ^ {20}$

  现选定一个正整数数$x$与原序列中所有的数异或

  然后问操作之后的序列相邻两数差的绝对值之和最小是多少

  在满足该值最小的前提下 最小的$x$又是多少

---------------------------------------------------------------------------------------

比赛时拉着一名队友磕了$1h+$ 然后乱写了一发就弃了

比赛后看了官方题解一脸懵逼 不过倒是根据题解的第一句话给的思路自己想了一种和题解实现不同的方法

我们知道 对于相邻两数 不同的最高位之前的位数异或后显然不改变答案 而之后的位数异或则不改变两数相对大小

于是可以用$delta[i][j], j \le i $表示第$i$位为最高不同位 仅第$j$位异或后总和的改变量

(换句话说就是把$n-1$对数压成$20$对数来算贡献)

如果第$i$位改变了$($异或了一个$1)$ 那么第$j$位贡献值取相反数

 

接下来比较直观的做法是枚举$x$然后对于每个$x$算出答案

然而扫一遍$delta$数组就是$20^2$ 再乘上$2^{20}$单$case$都不能$1s$跑完

于是考虑通过记忆化把部分结果重新利用

 

我们发现 如果现在考虑的是最高不同位在第$i$位的数对

那么两个不同的$x$只要最低的$i$位相同他们此时就是等价的 说明许多计算都是重复的

因此我们可以开一个$ans[i][x]$的数组来存储相应结果 然后转移进行一些预处理后也可以做到$O(1)$

 

这样复杂度是 $O($枚举$x * $枚举最高不同位$i *$均摊后只要枚举一次的记忆化搜索 $*$ 数据组数$T$)

$= 2^{20} * 20 * 10 = 2 * 10 ^ 8$ $3s$时限是充足的

不过按照刚才的想法 ans数组要开 $20 * 2 ^ {20}$ 空间要炸 因此可以改变枚举$($循环$)$的顺序 把$20$那一维消去

这样就可以通过此题了

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <cmath>
 4 #include <algorithm>
 5 using namespace std;
 6 const int M = 20, N = 1 << M;
 7 long long delta[M][M], ans[N], tans[N];
 8 int used[N];
 9 int mask[M], nexte[N];
10 int ans1;
11 long long ans2, sum;
12 int t, n, ca;
13 long long dfs(int b, int inv, int x)
14 {
15     if(used[x] == ca)
16         return ans[x] * inv;
17     used[x] = ca;
18     ans[x] = 0;
19     if(!x)
20         return 0;
21     int i = nexte[x];
22         ans[x] = delta[b][i] + dfs(b, 1, x ^ (1 << i));
23     return ans[x] * inv;
24 }
25 int main()
26 {
27     mask[0] = 1;
28     for(int i = 1; i < M; ++i)
29         mask[i] = mask[i - 1] * 2 + 1;
30     for(int i = 2; i < N; ++i)
31         nexte[i] = (i == (i & -i)) ? nexte[i - 1] + 1 : nexte[i - 1];
32     scanf("%d", &t);
33     while(t--)
34     {
35         memset(delta, 0, sizeof delta);
36         sum = 0;
37         int x, y, top, L, R;
38         scanf("%d", &n);
39         scanf("%d", &x);
40         for(int i = 2; i <= n; ++i)
41         {
42             scanf("%d", &y);
43             if(y == x)
44                 continue;
45             top = 19;
46             while((x ^ y ^ (1 << top)) > (x ^ y))
47                 --top;
48             L = min(x, y), R = max(x, y);
49             sum += R - L;
50             delta[top][top] += (L ^ (1 << top)) - (R ^ (1 << top)) - (R - L);
51             for(int j = top - 1; j >= 0; --j)
52                 if((x ^ y) & (1 << j))
53                     delta[top][j] += (R ^ (1 << j)) - (L ^ (1 << j)) - (R - L);
54             x = y;
55         }
56         int M2 = M;
57         while(M2 > 0 && !delta[M2 - 1][M2 - 1])
58             --M2;
59         int N2 = 1 << M2;
60         ans2 = (long long)N2 * n;
61         int inv;
62         for(int i = 0; i < N2; ++i)
63         {
64             inv = i & 1;
65             tans[i] = delta[0][0] * inv;
66         }
67         for(int j = 1; j < M2; ++j)
68         {
69             ++ca;
70             for(int i = 0; i < N2; ++i)
71             {
72                 inv = (i & (1 << j)) != 0;
73                 tans[i] += delta[j][j] * inv + dfs(j, (inv ? -1 : 1), i & mask[j - 1]);
74             }
75         }
76         for(int i = 0; i < N2; ++i)
77             if(tans[i] < ans2)
78             {
79                 ans2 = tans[i];
80                 ans1 = i;
81             }
82         ans2 += sum;
83         printf("%d %lld\n", ans1, ans2);
84     }
85     return 0;
86 }

 

posted @ 2016-08-05 14:09  sagitta  阅读(611)  评论(2编辑  收藏  举报