CF Round 765 Div2 题解
A题 Ancient Civilization
有 \(T(1\leq T \leq 100)\) 组数据。
定义 \(\operatorname{d}(x,y)\) 为数 \(x,y\) 间的距离,值为二进制下不同的位置的数量之和。(例如 \((10010)_2\) 和 \((01011)_2\),这两个数的距离为 3)。
现在,我们有 \(n\) 个数,第 \(i\) 个数记为 \(x_i\)。现在尝试求出一个数 \(y\),使得 \(\sum\limits_{i=1}^n\operatorname{d}(x_i,y)\) 最小,并输出 \(y\)。
\(n\leq 100,0\leq x_i,y < 2^l\),其中 \(l\) 为题目中给定的一个常数,且 \(1\leq l \leq 30\)
计算每一位的贡献,显然为了距离最小,优先让 \(y\) 的这一位和大多数保持一致(例如有 a 个数的第 xx 位为 1, b 个数的第 xx 位为 0,那么 \(y\) 的这一位应当根据 a 和 b 的大小关系来决定,哪个大选哪个)。对每一位均进行该操作,总复杂度为 \(O(Tln)\)。
#include<bits/stdc++.h>
using namespace std;
const int N = 110;
int n, l, x[N];
int solve() {
cin >> n >> l;
for (int i = 1; i <= n; ++i)
cin >> x[i];
int res = 0;
for (int i = 0; i < 30; ++i) {
int a = 0, b = 0;
for (int k = 1; k <= n; ++k)
if ((x[k] >> i) & 1) a++;
else b++;
if (a > b) res += 1 << i;
}
return res;
}
int main()
{
int T;
cin >> T;
while (T--) cout << solve() << endl;
return 0;
}
B题 Elementary Particles
有 \(T(1\leq T \leq 100)\) 组数据。
给定一个长度为 \(n\) 的数列 \(\{a_n\}\)。如果存在两个子区间 \([l_1,r_1],[l_2,r_2]\),他们互不相同(即不可能 \(l_1=l_2\) 且 \(r_1=r_2\))长度相同且存在某一位相同(例如 \([4,3,2,1]\) 和 \([5,3,4,8]\),这两个区间就是长度相等,且第二位相同),那么我们称这两个区间满足性质A。
问,我们在这个数列中能找到的最长的两个满足性质A的子区间的长度为多少?(无解时输出 -1
\(2\leq n \leq 150000,1\leq a_i\leq 150000,\sum n\leq 3*10^5\)
似乎只要某一位相同就能凑出一对区间,所以直接扫一遍,无相同元素则判无解。
如果有相同元素,那么即可以此进行拓展,来看看最多可以延申多长。我们假设位置 \(i,j\) (\(i<j\))的元素相同,那么区间长度最长为 \((n-j)+(i-1)+1=n-(j-i)\)。(也就是说,这个元素作为整个区间的第 \(i\) 个元素,此时区间长度最长,推导过程就是依次让 \(a_j\) 为区间的第 \(1,2,\cdots,i\) 项)
有了数学基础,那么问题就转变为了:找出两个相同元素,且他们在数列上的距离最短。受多组数据和智育所限,我们不妨开一个 \(map\) 来维护每个元素的上一个位置,每当扫到一个新元素的时候都更新一下,并重新计算最短距离。总复杂度为 \(O(n\log n)\)。
#include<bits/stdc++.h>
using namespace std;
const int N = 300010;
int n, a[N];
map<int, int> vis;
int solve() {
vis.clear();
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
int dis = n + 1;
for (int i = 1; i <= n; ++i) {
if (vis[a[i]]) dis = min(dis, i - vis[a[i]]);
vis[a[i]] = i;
}
return n - dis;
}
int main()
{
int T;
scanf("%d", &T);
while (T--) printf("%d\n", solve());
return 0;
}
C题 Road Optimization
题意略。
一个 DP,第一维记录当前要到第 \(i\) 个杆,第二位记录当前已经保留了 \(j\) 个杆(而不是拔掉了几个)。
这样写之后,状态转移方程就相对好写一些了:
(注:要到第 \(i\) 个杆,说明还没有到,所以这个杆子暂时不考虑拔不拔,留在以后转移再处理
别的怎么统计答案,输出啥的就不细说了,看代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 510;
int n, k;
LL len, d[N], a[N];
LL dp[N][N];
int main()
{
//read
cin >> n >> len >> k;
for (int i = 1; i <= n; i++)
cin >> d[i];
for (int i = 1; i <= n; i++)
cin >> a[i];
//solve
d[n + 1] = len;
memset(dp, 0x3f, sizeof(dp));
dp[1][0] = 0;
for (int i = 1; i <= n + 1; i++)
for (int j = 1; j < i; j++)
for (int x = 1; x <= j; x++)
dp[i][x] = min(dp[i][x], dp[j][x - 1] + a[j] * (d[i] - d[j]));
LL ans = 1e18;
for (int i = 0; i <= k; i++)
ans = min(ans, dp[n + 1][n - i]);
cout << ans;
return 0;
}