E https://codeforces.com/contest/1654/problem/E
给一数列,修改尽量少的值让整个数列等差
范围 n <= 1e5
1<= ai <= 1e5
5s
1GB
题解 实际上是二维点(i,ai),找直线经过最多的点
考虑增量 小于 sqrtn,那么选中斜率, 计算每个点沿着斜率与y轴交点 出现的最多次即可, 不要用map,用数组和清理数组实现
考虑增量 大于 sqrtn,以每个点作为起点,那么最多尝试这个点向后n/sqrt(n) 个点, 把这些点计算出现最多次数的斜率, 数量不多,可以用map
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 #include <bits/stdc++.h> using namespace std;typedef long long ll;#define MOD 1000000007 #define rep(i,a,n) for (ll i=a;i<n;i++) #define per(i,a,n) for (ll i=n-1;i>=a;i--) #define pb push_back const double pi = acos (-1.0 );const int N = 100000 ;int a[N+10 ];int used[32000010 ];int n;int work () { int SQRT = 316 ; int ans = 0 ; rep (i,0 ,SQRT+1 ){ vector<int > rm; rep (j,0 ,n){ int v = a[j] + SQRT*N- j*i; if (!used[v])rm.push_back (v); ans = max (ans,++used[v]); } for (auto v:rm){ used[v] = 0 ; } } rep (j,0 ,n){ map<int ,int > kcnt; rep (i,j+1 ,n){ if (a[j] + (i-j) * SQRT > N) break ; if ( (a[i] - a[j]) % (i-j) == 0 ){ kcnt[(a[i] - a[j]) / (i-j)]++; } } for (auto [k,cnt]:kcnt){ ans = max (ans, cnt+1 ); } } return ans; } int main () { cin>>n; rep (i,0 ,n){ scanf ("%d" ,a+i); } int ans = work (); rep (i,0 ,n/2 ){ swap (a[i],a[n-1 -i]); } printf ("%d\n" ,n - max (ans,work ())); return 0 ; }
总结 按照一个值,分段处理,每一段都是性能满足的
参考 官方题解
F https://codeforces.com/contest/1654/problem/F
给一个字符串,长度$2^n$
找一个$j <= 2^n$, 让s[i^j]
字典序最小
范围 n <= 18
3s
512MB
题解 我的尝试 我想到,j的位是0或1实际上意味着 原字符串 的长度2^k 区间是否交换
而判断是否交换,可以局部贪心
问题是,存在字符相等的情况, 不知道怎么记录, 试了一下random 并不能骗分哈哈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 #include <bits/stdc++.h> using namespace std; typedef long long ll;#define MOD 1000000007 #define rep(i,a,n) for (ll i=a;i<n;i++) #define per(i,a,n) for (ll i=n-1;i>=a;i--) #define pb push_back const double pi = acos (-1.0 ); int n;char s[270010 ];int ans[270010 ][20 ]; int cmp (int p,int pwr,int l,int r) { rep (i,0 ,(1 <<pwr)){ if (s[(p + i) ^ l] < s[(p + (1 <<pwr) + i) ^ r]) return -1 ; if (s[(p + i) ^ l] > s[(p + (1 <<pwr) + i) ^ r]) return 1 ; } rep (i,0 ,(1 <<pwr)){ if (s[(p + (1 <<pwr) + i) ^ l] < s[(p + i) ^ r]) return -1 ; if (s[(p + (1 <<pwr) + i) ^ l] > s[(p + i) ^ r]) return 1 ; } return 0 ; } ll tryRand () { rep (i,1 ,n+1 ){ for (int p = 0 ;p < (1 <<n); p += (1 <<i)){ int lans = ans[p][i-1 ]; int rans = ans[p+(1 <<(i-1 ))][i-1 ]; int res = cmp (p,i-1 ,lans,rans); if (res == 0 ){ if (rand ()%2 ){ ans[p][i] = lans; }else { ans[p][i] = (rans | (1 <<(i-1 ))); } } else if (res == -1 ){ ans[p][i] = lans; }else { ans[p][i] = (rans | (1 <<(i-1 ))); } } } return ans[0 ][n]; } int main () { srand (time (NULL )); cin>>n; scanf ("%s" ,s); int j = tryRand (); rep (t,0 ,180 ){ int k = tryRand (); rep (i,0 ,(1 <<n)){ if (s[i ^ j] < s[i ^ k])break ; if (s[i ^ j] == s[i ^ k])continue ; j = k; break ; } } rep (i,0 ,(1 <<n)){ printf ("%c" , s[i ^ j]); } printf ("\n" ); return 0 ; }
正解 实际上可以看成, 把 0~n-1
排序
而排序是按照f(s,i)
的字典序来比较大小的
这里的问题是 如果两两比较 依然复杂度完成不了, 如何减少比较和处理相等
考虑样例1: acba
1 2 3 4 0: acba 1: caab 2: baac 3: abca
最终期望得到
1 2 3 4 3: abca 0: acba 2: baac 1: caab
然而注意到,
首位的比较 就是 s[i]
第2位的比较 就是 s[i ^ (1<<0)]
第3位的比较 就是 s[i ^ (1<<1)]
第4位的比较 就是 s[i ^ (1<<2)]
而 一旦首位有非相等的大小关系了,之后也就不会改变相对顺序, 不想等时 才考虑后续排序
于是, 按首位排序
变为
记录大小顺序
1 2 3 4 0: a(0) 3: a(0) 2: b(1) 1: c(2)
按照第二位排序
得到
整体变为(注意首位不相等的要保持顺序)
记录大小顺序
1 2 3 4 3: ab(0) 0: ac(1) 2: ba(2) 1: ca(3)
这样所有位排序, 更新顺序即可
换句话说就是类似基数排序, 每次是对前缀相等的一系列值的下一位 进行排序
这里注意到看上去字符串长度,有1 << n
, 但是实际上,上述排序只需要考虑2的幂次的
因为,[2..3] 的排序 实际是 ([0..1]^2)得到的排序
因为,[4..7] 的排序 实际是 ([0..3]^4)得到的排序
所以 虽然是基数排序,但是 能通过幂次 和记录大小的排序值 优化比较次数
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <bits/stdc++.h> using namespace std;typedef long long ll;#define MOD 1000000007 #define rep(i,a,n) for (ll i=a;i<n;i++) #define per(i,a,n) for (ll i=n-1;i>=a;i--) #define pb push_back const int N = 1 << 18 ;int n, a[N], val[N], tmp[N], now;char s[N];bool cmp (int x, int y) { return (val[x] == val[y]) ? val[x ^ now] < val[y ^ now] : val[x] < val[y]; } int main () { cin>>n; scanf ("%s" , s); rep (i,0 ,1 <<n){ val[i] = s[i] - 'a' ; a[i] = i; } rep (pwr,0 ,n) { now = 1 << pwr; sort (a, a + (1 << n), cmp); int num = 0 ; rep (j,0 ,1 <<n){ if (j > 0 && cmp (a[j - 1 ], a[j])) num ++; tmp[a[j]] = num; } rep (j,0 ,1 <<n){ val[j] = tmp[j]; } } rep (i,0 ,1 <<n){ putchar (s[i ^ a[0 ]]); } return 0 ; }
pwr = 0, now = 1
如果字符 更小,则排在前面
如果字符 相等,则它^1位置的更小就排在前面, 如果依然相等,则认为当前论次比较它们两相等
修改val[i] = 表示选定异或值i
, 让 s
在i 的转换下, 前两位 在所有i 中的 顺序
pwr = 1, now = 2
注意到上面val意义已经变化
val[i] 表示的是 i 引起的转换 的前两位的排序值
如果排序值更小,则排在前面
如果排序值相等,则它^2位置的更小就排在前面, 如果依然相等,则认为当前论次比较它们两相等
修改val[i] = 表示选定异或值i
, 让 s
在i 的转换下, 前4位 在所有i 中的 顺序
pwr = 2, now = 4
val[i] 表示的是 i 引起的转换 的前4位的排序值
如果排序值更小,则排在前面
如果排序值相等,则它^4位置的更小就排在前面, 如果依然相等,则认为当前论次比较它们两相等
修改val[i] = 表示选定异或值i
, 让 s
在i 的转换下, 前8位 在所有i 中的 顺序
pwr = n, now = 2**n
val[i] 表示的是 i 引起的转换 的前now位的排序值
如果排序值更小,则排在前面
如果排序值相等,则它xor now 位置的更小就排在前面, 如果依然相等,则认为当前论次比较它们两相等
修改val[i] = 表示选定异或值i
, 让 s
在i 的转换下, 前now*2
位 在所有i 中的 顺序
总结 每次排序,再记录顺序, 方便后续比较,而不仅仅排序就完了, 这个记录顺序很重要
参考 官方题解