2020-2021 ICPC, NERC, Northern Eurasia Onsite BEIJ 題解
B. Button lock
題意:有 \(d\) 個 01 按鍵以及一個 reset 按鍵,你需要把所有題目給定的 \(n\) 個密碼全部表示一遍。只有按下 reset 按鍵後才能使所有 01 按鍵彈回。試使得按鍵次數最少。
做法:可以觀察到 \(ans = \sum_{u \in endvertex} (bitcount(u) + 1) - 1\),接下來要儘可能使 ans 最小。
二分圖匹配演算法可以保證其路徑數最少,接下來我們考慮二分圖匹配與匈牙利演算法。
先設左邊的點集為 S,右邊的點集為 T。假如做完了二分圖匹配,那麼若左邊點集 S 中有未匹配到的點,那麼說明這個點是一條路徑的結尾。這樣,答案變成了使得左邊點集 S 中未匹配的點的 1 的個數之和最少。
由於在匈牙利演算法中,我們依次列舉左側點集中的點,一個之前匹配到的點不可能再失配,而一個失配的點在之後還是失配。這樣,可以貪心地將所有密碼按照 1 的個數從大到小排序,然後再做匈牙利演算法。二分圖匹配演算法可以使路徑數最少,而上述排序使得失配的左側點的對答案的貢獻和儘可能小。
時間複雜度是 \(O(VE) = O(2^d3^d) = O(6^d)\)
上面少證明了一個東西:是否 ans 最小的時候滿足路徑數最少呢?反證法,若 ans 最小的時候路徑數不是最小的,那麼可想而知是在二分圖匹配中有一些更多的點失配了,在最大匹配的時候失配的點仍然失配;或是這個點雖然匹配上了,但是含有更多 1 的點失配了。這樣答案只會比滿足路徑數最少的條件時的答案更大。
#include <bits/stdc++.h> using namespace std; const int N = 1030; const int D = 15; #define IL inline #define pb push_back #define mk make_pair #define fi first #define se second #define dbg1(x) cout << #x << " = " << x << ", " #define dbg2(x) cout << #x << " = " << x << endl int d, n; int a[N]; IL bool in(int x, int y) { return (x & y) == x; } IL int bitcount(int x) { return x == 0 ? 0 : bitcount(x >> 1) + (x & 1); } struct Hungary { int n, m; int Left[N], Right[N]; bool S[N], T[N]; vector<int> G[N]; IL void init(int n, int m) { this -> n = n; this -> m = m; fill(Left, Left + 1 + m, 0); fill(Right, Right + 1 + n, 0); fill(S, S+n+1, 0); fill(T, T+m+1, 0); for(int i=1;i<=n;i++) G[i].clear(); } IL void AddEdge(int u, int v) { G[u].pb(v); } bool match(int u) { //dbg2(u); S[u] = true; for(auto v : G[u]) if(!T[v]) { //dbg1(u); dbg2(v); T[v] = true; if(Left[v] == 0 || match(Left[v])) { Left[v] = u; Right[u] = v; return true; } } return false; } int gao() { int ans = 0; fill(Left, Left+1+m, 0); fill(Right, Right + 1 + n, 0); for(int i=1;i<=n;i++) { fill(T, T+1+m, 0); if(match(i)) ans++; } return ans; } vector<int> ans; int lb(int x) { return x & (-x);} void dfs(int u) { int v = Right[u]; //dbg1(u); dbg2(v); if(v == 0) { ans.pb(0); return;} for(int val = (a[u] ^ a[v]), j = __lg(val); val; val -= (1 << j), j = __lg(val)) { //dbg1(val); dbg2(j); ans.pb(d - j); } dfs(v); } void solve() { //puts("wtf"); int matchcnt = gao(); //dbg1(matchcnt); for(int i=1;i<=n;i++) { if(Left[i] != 0) continue; for(int val = a[i], j = __lg(val); val; val -= (1 << j), j = __lg(val)) { //dbg1(i); dbg1(val); dbg2(j); ans.pb(d - j); } dfs(i); } printf("%d\n", (int)ans.size()-1); for(int i=0;i<(int)ans.size()-1;i++) { if(ans[i] == 0) putchar('R'); else printf("%d", ans[i] - 1); printf("%c", " \n"[i == (int)ans.size() - 2]); } } }; Hungary lpr; int main() { //freopen("B2.in", "r", stdin); //freopen("B2.out", "w", stdout); scanf("%d%d", &d, &n); lpr.init(n, n); for(int i=1;i<=n;i++) { for(int j=1;j<=d;j++) { int v; scanf("%1d", &v); a[i] = (a[i] << 1) | v; } } sort(a+1, a+1+n, [](int a, int b) { return bitcount(a) > bitcount(b);}); //for(int i=1;i<=n;i++) {dbg1(i); dbg2(a[i]);} for(int i=1;i<=n;i++) { for(int j=1;j<i;j++) { if(!in(a[i], a[j])) continue; lpr.AddEdge(i, j); } } lpr.solve(); return 0; }
E. Equilibrium Point /\/\
題意:略
前置知識:多邊形的重心。每個三角形的
做法:可以發現合法括號序列的個數是卡特蘭數。在 n = 18 的時候,卡特蘭數為 477638700。那麼,是否可以使用類似卡特蘭數定義式的方式去做這個題呢?PS:\(f(n+1) = f(0)f(n) + f(1)f(n-1) + ... + f(n)f(0)\)
然後可以發現似乎有一些情況下重心是一樣的。也就是說可以把一些重心一樣的括號序列去掉。
由卡特蘭遞推式,我們考慮現在有 L 和 R 兩個括號序列,那麼接下來我們將它們合併起來,新的括號序列的重心是什麼樣子的呢?可以發現新的括號序列跟原來的括號序列所圍成的面積 \(m_L, m_R\),原來的重心 \((x_L, y_L), (x_R, y_R)\),以及 L 的長度 \(2i\) 相關。
\[m_{new} = m_L + m_R \\ x_{new} = x_L \frac{m_L}{m_{new}} + (x_R+2i) \frac{m_R}{m_{new}} \\ y_{new} = y_L \frac{m_L}{m_{new}} + y_R \frac{m_R}{m_{new}} \]還有一種情況是假如現在只有 L 括號序列,那麼新的括號序列為 (L)
\[m_{new} = m_L + 2i - 1 \\ x_{new} = (x_L + 1) \frac{m_L}{m_{new}} + i \frac{2i-1}{m_{new}} \\ y_{new} = (y_L + 1) \frac{m_L}{m_{new}} + \frac{i - \frac{2}{3}}{2i-1} \cdot \frac{2i-1}{m_{new}} \]也就是說,我們需要搞出來有 \(i\) 對括號的所有三元組 \((m, x, y)\) 。通過類似卡特蘭數定義式的列舉方法,可以預處理出來所有有 \(i\) 對括號情況下的三元組 \((m,x,y)\)。為了使得三元組唯一,請使用手寫雜湊表而不是STL,否則會TLE。這樣,跑完之後發現只枚舉了 79079257 次。即可解開此題。
另外,由於跑三元組的時候一方面由於精度問題,另一方面由於運算速度問題,最好避免使用浮點數。所以在維護三元組的時候,我們維護的東西其實是 \((m, mx, 3my)\),具體式子請自行帶入上方的式子推一推。
#include <bits/stdc++.h>
using namespace std;
#define mk make_pair
#define dbg1(x) cout << #x << " = " << x << ", "
#define dbg2(x) cout << #x << " = " << x << endl
typedef tuple<int, int, int> tiii;
typedef pair<tiii, tiii> pi6;
typedef long long ll;
const int mod = 10000007;
int cnt = 0;
int n;
double x, y;
struct Hash_table {
int n, now;
int hd[mod];
int nxt[mod];
int tim[mod];
ll v[mod];
void init() { n = 0; now++;}
bool insert(ll val) {
int s = val % mod;
if(tim[s] != now) { tim[s] = now; hd[now] = 0;}
for(int i=hd[s]; i!=0; i=nxt[i]) {
if(v[i] == val) return false;
}
nxt[n + 1] = hd[s];
v[n + 1] = val;
hd[s] = ++n;
return true;
}
}HASH;
vector<tuple<int, int, int> > a[20];
string solve(int i, tiii p) {
if(i == 0) return "";
for(auto b : a[i-1]) {
int ml = get<0>(b), xl = get<1>(b), yl = get<2>(b);
int mnew = ml + 2 * i - 1;
int xnew = xl + ml + i * (2 * i - 1);
int ynew = yl + 3 * ml + 3 * i - 2;
if(tiii(mnew, xnew, ynew) == p) {
return "(" + solve(i - 1, b) + ")";
}
}
for(int j=1;j<i;j++) {
for(auto b1 : a[j]) for(auto b2 : a[i-j]) {
int ml = get<0>(b1), mr = get<0>(b2);
int xl = get<1>(b1), xr = get<1>(b2);
int yl = get<2>(b1), yr = get<2>(b2);
int mnew = ml + mr;
int xnew = xl + xr + 2 * j * mr;
int ynew = yl + yr;
if(tiii(mnew, xnew, ynew) == p) {
return solve(j, b1) + solve(i - j, b2);
}
}
}
assert(0);
}
void add(int i, int mm, int xx, int yy) {
long long v = mm * (1ll << 40ll) + xx * (1ll << 20ll) + yy;
if(HASH.insert(v)) a[i].push_back(tiii(mm, xx, yy));
}
int main() {
#ifdef LOCAL
freopen("E.in", "r", stdin);
// freopen("E.out", "w", stdout);
#endif
clock_t aa = clock();
a[0].push_back(tiii(0, 0, 0));
for(int i=1;i<=18;i++) {
HASH.init();
for(int j=1;j<i;j++) { // the size of left bracket sequence.
for(auto b1 : a[j]) for(auto b2 : a[i-j]) {
int ml = get<0>(b1), mr = get<0>(b2);
int xl = get<1>(b1), xr = get<1>(b2);
int yl = get<2>(b1), yr = get<2>(b2);
int mnew = ml + mr;
int xnew = xl + xr + 2 * j * mr;
int ynew = yl + yr;
add(i, mnew, xnew, ynew);
}
cnt += a[j].size() * a[i-j].size();
}
// ( sequence )
for(auto b : a[i-1]) {
int ml = get<0>(b), xl = get<1>(b), yl = get<2>(b);
int mnew = ml + 2 * i - 1;
int xnew = xl + ml + i * (2 * i - 1);
int ynew = yl + 3 * ml + 3 * i - 2;
add(i, mnew, xnew, ynew);
}
cnt += a[i-1].size();
dbg1(i); dbg1(a[i].size()); dbg2(cnt);
}
clock_t b = clock();
// cerr << "running " << b - aa << "ms\n";
scanf("%d%lf%lf", &n, &x, &y);
n /= 2;
for(auto b : a[n]) {
int bm = get<0>(b), bx = get<1>(b), by = get<2>(b);
double xx = 1.0 * bx / bm, yy = 1.0 / 3.0 * by / bm;
if(fabs(xx - x) + fabs(yy - y) <= 1e-8) {
printf("%s\n", solve(n, b).c_str());
return 0;
}
}
return 0;
}
I. Is It Rated?
題意:略
做法:神奇的做法。演算法名稱叫做 Weighted Majority Algorithm,也可以百度天氣預測問題。但是百度到的證明的數學符號似乎都崩了,所以我看不懂這演算法是咋證明的……
程式碼實現中是給每一個人賦值一個他這一輪的可信權值,然後對 n 個人遍歷一遍得出這一輪選 0 的概率。然後再整一個 \([0,1]\) 的隨機數去跟上面的概率比對選 0/1.
這一輪結束後,如果對了那麼他的可信權值不用管,否則他的可信權值乘一個係數 \(\alpha\)。題解中說 \(\frac{1}{2} < \alpha < \frac{19}{20}\)。程式碼中取的 0.8。
#include <bits/stdc++.h>
using namespace std;
const int N = 1000 + 5;
int n, m;
char s[N];
long double a[N];
int main() {
srand(time(0));
scanf("%d%d", &n, &m);
for(int i=1;i<=n;i++) a[i] = 1e18;
for(int i=1;i<=m;i++) {
scanf("%s", s + 1); getchar();
long double tot0 = 0;
long double tot = 0;
for(int j=1;j<=n;j++) {
if(s[j] == '0') tot0 += a[j];
tot += a[j];
}
if(rand() * tot < tot0 * RAND_MAX) putchar('0');
else putchar('1');
putchar(10);
fflush(stdout);
char ans; ans = getchar();
for(int j=1;j<=n;j++) if(s[j] != ans) {
a[j] *= 0.8;
}
}
return 0;
}
J. Japanese Game
題意:略
做法:
假如說現在有 profile 了,怎麼弄出來 mask?
顯然可以得出按照最左邊放置的放法,然後再得出按照最右邊放置的放法,然後看看哪些一直都是 # 的方塊仍然還是屬於一個大 # 塊的,這些方塊在 mask 中仍然是 #。
考慮 \(O(n^2)\) 做法。列舉按照最左邊放置的放法,在 mask 中顯示 # 的格子需要向左移動 \(i\) 格,而小於等於 \(i\) 格的 # 全部都被刪掉了需要加回來。這樣只需要分開考慮每一個 _ 塊怎麼填即可。
- 如果 _ 在邊上且長度為1,無解。
- 如果 __ 在中間且長度為 2,無解。
- 對每一個 _ 塊加入長度為 1 或 2 的 # 塊,一邊加一邊判斷。
可以發現,若在 \(i \geq 4\) 時候有答案成立,一定有 \(i' = i - 2\) 有答案成立。那麼時間複雜度 \(O(n)\)
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define dbg1(x) cout << #x << " = " << x << ", "
#define dbg2(x) cout << #x << " = " << x << endl
const int N = 100000 + 5;
const int inf = 1e9 + 7;
int n;
char ss[N];
char s[N];
struct Node {
int l;
char ch;
Node(int l=0, char ch='\0'):l(l), ch(ch) {}
};
bool check(int lift) {
for(int i=1;i<=n;i++) s[i] = ss[i];
for(int i=1;i<=n;i++) if(s[i] == '#') {
for(int j=1;j<=lift;j++) s[i-j] = '#';
}
s[n-lift+1] = '\0';
vector<Node> a;
for(int i=1, j=1; i<=n-lift; i++) {
if(s[i] == s[j] && s[i+1] != s[i]) {
int len;
if(s[j] == '_') {
if(i == j) {
if(j == 1 || i == n - lift) return false;
else { j = i + 1; continue;}
}
if((j != 1 && i != n - lift) && i - j + 1 == 2) return false;
len = i - j + (j == 1) + (i == n - lift);
}
else len = i - j + 1;
a.pb(Node(len, s[j]));
j = i + 1;
}
}
vector<int> ans;
for(int i=0;i<a.size();i++) {
if(a[i].ch == '#') { ans.push_back(a[i].l); continue;}
int len = a[i].l;
for(int j=0;j<len/2-1;j++) {
ans.pb(1);
if(lift < 1) return false;
}
if(len % 2 == 1) {
ans.pb(2);
if(lift < 2) return false;
}
else {
ans.pb(1);
if(lift < 1) return false;
}
}
printf("%d\n", (int)ans.size());
for(int i=0;i<ans.size();i++) {
printf("%d%c", ans[i], " \n"[i == (int)ans.size()-1]);
}
return true;
}
int main() {
#ifdef LOCAL
freopen("J.in", "r", stdin);
#endif
scanf("%s", ss + 1);
n = strlen(ss + 1);
int lift = inf;
if(ss[1] == '#' || ss[n] == '#') lift = 0;
for(int i=1, j=1;i<=n;i++) {
if(ss[j] == '_' && ss[i] == '_' && i == n) {
lift = min(lift, i - j + 1);
}
else if(ss[j] == '_' && ss[i] == '#') {
if(j == 1) lift = min(lift, i - j);
else lift = min(lift, i - j - 1);
}
if(ss[j] != ss[i]) j = i;
}
for(int k=0;k<=min(lift, 3); k++) {
if(check(k)) return 0;
}
puts("-1");
return 0;
}