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\) 個杆(而不是拔掉了幾個)。
這樣寫之後,狀態轉移方程就相對好寫一些了:
\[dp(i,x)=\max_{1\leq j < i} dp(j,x-1)+a_j(d_i-d_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;
}