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 中的 顺序
总结
每次排序,再记录顺序, 方便后续比较,而不仅仅排序就完了, 这个记录顺序很重要
参考
官方题解