Educational Codeforces Round 115 (Rated for Div. 2)(題解)
Educational Codeforces Round 115 (Rated for Div. 2)
A - Computer Game
思路:dfs暴力搜尋。
\(Code:\)
const int N = 1010; int _,n; char a[3][N]; bool st[3][N]; int l[] = {0,0,-1,1,1,1,-1,-1},r[] = {1,-1,0,0,1,-1,1,-1}; void dfs(int x,int y){ st[x][y] = true; for(int i=0;i<=7;++i){ int idx = l[i] + x,idy = r[i] + y; if(idx > 2 or idx <= 0 or idy > n or idy <= 0)continue; if(st[idx][idy])continue; if(a[idx][idy] == '1')continue; dfs(idx,idy); } } void solve(){ read(_); while(_--){ read(n); memset(a,-1,sizeof a); memset(st,0,sizeof st); scanf("%s%s",a[1]+1,a[2]+1); if(a[1][1] == '0') dfs(1,1); if(st[2][n] == 1)puts("YES");else puts("NO"); } }
思路:暴力列舉答案,然後設答案為\((l,r)\) 那麼檢測答案是否合法。只有當所有人都至少在\((l,r)\)其中一天內有空,並且在這兩天有空的總人數均大於等於\(\frac{n}{2}\) ,那麼答案就合法。如果不理解可以自己畫圖感受一下。
\(Code:\)
const int N = 100100; int _,n; int a[N][6]; bool st[33]; int l[] = {0,0,-1,1,1,1,-1,-1},r[] = {1,-1,0,0,1,-1,1,-1}; void solve(){ read(_); while(_--){ read(n); rep(i,1,n){ rep(j,1,5){ read(a[i][j]); } } bool flag = false; for(int i=1;i<(1<<5);++i){ int cnt = 0; int now[55],hh = 0; for(int j=0;j<=4;++j){ if((i>>j)&1){ now[++hh] = j+1;cnt++; } } bool judge = true; int x[33];x[1] = x[2] = 0; if(cnt == 2){ //rep(j,0,4)write((i>>j)&1);pc('\n'); for(int j=1;j<=n;++j){ if(!a[j][now[1]] and !a[j][now[2]]){ judge = false;break; } x[1] += (a[j][now[1]] == 1); x[2] += (a[j][now[2]] == 1); } }else continue; if(x[1] < n/2 or x[2] < n/2)judge = false; if(judge){flag = true;break;} } if(flag)puts("YES");else puts("NO"); } }
思路:因為他讓選擇兩個數,我們首先判斷是否有答案,由平均數定義可得:
\[\frac{\sum_1^n a[i]}{n} = \frac{a[x] + a[y]}{2} \]所以:
\[a[x] + a[y] = \frac{2\sum_1^na[i]}{n} \]因為陣列\(a\)是整數,那麼首先判斷是否\(a[x] + a[y]\)可以為整數,然後開個桶記錄所有個數,依次列舉每一個\(a[i]\)作為答案。即可得到答案
複雜度\(O(nlogn)\)
\(Code:\)
const int N = 200100; int _,n; ll a[N]; bool st[33]; int l[] = {0,0,-1,1,1,1,-1,-1},r[] = {1,-1,0,0,1,-1,1,-1}; map<ll,ll>S; void solve(){ read(_); while(_--){ S.clear(); read(n); ll Sum = 0ll; rep(i,1,n){ read(a[i]);S[a[i]]++; Sum += a[i]; } if((Sum * (2ll))%n){ puts("0");continue; } ll x = (Sum * (2ll))/n; //printf("%lld\n",x); ll ans = 0; rep(i,1,n){ ll now = x - a[i] ; ll res = S[now]; if(now == a[i]){ res--; } ans+=res; } write(ans/2);pc('\n'); } }
思路:這個題是讓我們選合法三元組數,那麼正難則反,我們考慮不符合的個數。首先我們發現如果三元組有一個屬性全部相同,那麼該三元組一定合法。比如屬性\(A[x,x,x]\) ,那麼屬性\(B\)一定是不相同的,因為如果相同就不滿足題目所給限制:任意兩個問題\(A\)屬性和\(B\)屬性至少有一個不同。那麼我們不符合的個數裡只有這種形式\(A[x,x,y],B[a,b,b]\),即兩個屬性是兩個相同的,那我們發現我們可以列舉作為中間的,因為在中間的元素右邊必定與其\(A\)屬性相同,左邊必定與其\(B\)屬性相同,開個桶分別記錄兩個屬性滿足的個數,所以不符合的個數應該是兩邊相乘得到的結果。最後用總個數減去不符合的個數。
複雜度\(O(n)\)
\(Code:\)
const int N = 200100;
int _,n;
ll a[N];
bool st[33];
int l[N],r[N];
map<ll,ll>S;
pair<int,int>arr[N];
void solve(){
read(_);
while(_--){
read(n);
fill(l,l+1+n,0);fill(r,r+1+n,0);
ll sum = (1ll *n*(n-1)*(n-2))/6;
rep(i,1,n){
int x,y;
read(x);read(y);
l[x]++;r[y]++;
arr[i] = {x,y};
}
ll res = 0ll;
rep(i,1,n){
int x = arr[i].first,y = arr[i].second;
res += 1ll*(l[x]-1)*(r[y]-1);
}
write(sum-res);pc('\n');
}
}
思路:這個題感覺比較好想,重點應該在程式碼實現上,將梯子向上、向下搜尋。然後在第一個不自由的格子停住,然後相乘維護答案。因為詢問\(10^4\),並且每次詢問暴力複雜度為\(O(n)\) 總複雜度為\(O(n*q)\) ,可以通過。
\(Code:\)
const int N = 1010;
int _, n, m, q;
bool st[N][N];
ll a[N];
int l[] = {-1, 0, 1, 0}, r[] = {0, -1, 0, 1};
ll ans;
void work(int x, int y) {
// 2~3 1~4
int idx = x, idy = y;
ll a1 = 0, a2 = 0, b1 = 0, b2 = 0;
int now = 0;
bool tmp = st[x][y];
st[x][y] = false;
while (idx >= 1 and idx <= n and idy >= 1 and idy <= m and
st[idx][idy] == false) {
a1++;
idx += l[now];
idy += r[now];
now++;
if (now >= 2) now = 0;
}
now = 1;
idx = x, idy = y;
while (idx >= 1 and idx <= n and idy >= 1 and idy <= m and
st[idx][idy] == false) {
a2++;
idx += l[now];
idy += r[now];
now++;
if (now >= 2) now = 0;
}
now = 2;
idx = x, idy = y;
while (idx >= 1 and idx <= n and idy >= 1 and idy <= m and
st[idx][idy] == false) {
b2++;
idx += l[now];
idy += r[now];
now++;
if (now >= 4) now = 2;
}
idx = x;
idy = y;
now = 3;
while (idx >= 1 and idx <= n and idy >= 1 and idy <= m and
st[idx][idy] == false) {
b1++;
idx += l[now];
idy += r[now];
now++;
if (now >= 4) now = 2;
}
if (tmp == true) {
ans += a1 * b1 + a2 * b2 - 1;
} else {
ans -= a1 * b1 + a2 * b2 - 1;
}
st[x][y] = !tmp;
}
void solve() {
read(n);
read(m);
read(q);
rep(i, 1, max(n, m)) {
ll x1 = max(1ll * (n - i + 1) * (m - i + 1), 0ll);
if (i != 1) x1 *= 2ll;
ll x2 = max(1ll * (n - i + 1) * (m - (i + 1) + 1), 0ll) +
max(0ll, 1ll * (n - (i + 1) + 1) * (m - i + 1));
// printf("%d:: %lld %lld\n",i,x1,x2);
ans += x1 + x2;
}
rep(test, 1, q) {
int x,y;
read(x);read(y);
work(x,y);
write(ans);pc('\n');
}
}
思路:我們定義$ "("$的權值為1, \(")"\)的權值為-1。我們定義合法字串\(s\)為\(s\)的所有字首的權值和的最小值大於等於0。只有合法字串才能夠更新答案,否則該字串的答案就已經確定了,就沒必要再進行更新。
- 例如:\((()))\)是一個非法字串,\((())(\)是一個合法字串。
考慮動態規劃,因為最多隻有20個串,所以我們可以用二進位制\(01\)來表示集合。設\(f[i]\)表示選擇集合\(i\)的最佳答案。我們再維護一個\(g\)陣列,表示在當前集合取最佳答案時的權值和為多少。特別的,如果為非法字串,那麼\(g[i] = -1\)。考慮轉移,根據一般動態規劃特性,我們應該列舉該集合中哪一個串是出現在最後。然後用這個來更新答案。我們假設列舉的串編號為\(t\) 那麼我們轉移的集合應該是\(i \bigoplus (1<<t)\),那麼分類討論:
- \(g[i \bigoplus (1<<t)] = -1\),那麼說明已經是非法字串了,答案不會再增加,所以不用轉移。
- \(g[i\bigoplus(1<<t)] + min[t] < 0\),注:這裡的\(min[t]\)是指字串\(t\)的最小字首和。那麼這表明,雖然\(1\bigoplus(1<<t)\)並不是非法字串,但是最後加入字串\(t\)之後,就變成了非法字串,那麼我們可以更新\(ans\) ,維護兩個桶,第一個桶簡單代表字首和的出現次數,第二個桶代表第一次出現比\(i\)小的字首和時,\(i\)的個數。我們這裡主要是用第二個桶來完成更新。因為我們已經知道該集合的權值和,那麼又知道了經過這一次更新,將變為非法字串,那麼在字串\(t\)中,肯定有一個位置,在這個位置之前,原串是合法字串,在這個位置之後,原串是非法字串,那我們第二個桶記錄\(i\)總是在第一個桶第一次記錄\(i-1\)時,就是表明在字首和最低降到\(i\)時,有多少個位置的字首和為\(i\)。我們不難寫出轉移方程\(sum = f[i\bigoplus(1<<t)] + S2[t][-g[i\bigoplus(1<<t)]]\) 。
- \(g[1\bigoplus(1<<t] + min[t] \geq 0\) ,這說明經過這輪更新之後,依舊是合法字串,那麼我們可以更新該字串的\(f,g\)陣列,並且用第一個桶進行維護答案。
因為維護桶用的是\(unorderedmap\),所以複雜度\(O(n2^{20})\)
\(Code:\)
string a[33];
int n;
int mi[33], Sum[33];
int f[(1 << 21)], g[(1 << 21)];
unordered_map<int, int> S1[22], S2[22];
void solve() {
ios::sync_with_stdio(false);
cin >> n;
rep(i, 0, n - 1) {
cin >> a[i];
int now = 0;
for (auto j : a[i]) {
if (j == '(')
now++;
else
now--;
mi[i] = min(mi[i], now);
//記錄字首個數
S1[i][now]++;
//記錄第一次不能統計now+1時的數量
if (S1[i][now] == 1) {
S2[i][now + 1] = S1[i][now + 1];
}
}
Sum[i] = now;
}
int ans = 0;
for (int i = 1; i < (1 << n); ++i) {
g[i] = -1;
//列舉第j個做集合i的最後一個字串
for (int j = 0; j < n; ++j) {
if ((i >> j) & 1) {
int id = i ^ (1 << j);
if (g[id] < 0) {
continue;
} else if (g[id] + mi[j] < 0) {
int sum = S2[j][-g[id]] + f[id];
ans = max(ans, sum);
} else {
int res = S1[j][-g[id]] + f[id];
if (f[i] < res) {
f[i] = res;
}g[i] = Sum[j] + g[id];
ans = max(ans,f[i]);
}
}
}
}
write(ans);pc('\n');
}
思路:設\(A+B=X\)且\(A \geq B\),那麼\(A,B\)的長度可以有以下幾種
- \(|A| = |X|-1,|B| = |X|-1\)
- \(|A| = |X|\)
那麼第一種情況,我們可以列舉在\(a\)串中所有長度為\(|X|-1\)的串來作為答案。重點考慮第二種情況,我們列舉所有長度為\(|X|\)的串為\(A\)來判斷,我們可以發現確定\(A,X\)之後,\(B\)的長度其實是由\(X\)與\(A\)的最長公共字首決定的,因為沒有\(0\)的存在,所以\(|A|-lcp(A,X)_{len}\)即為\(B\)的長度,在確定\(B\)的長度以及\(B\)的位置後,我們可以取出\(B\)來進行判斷。\(B\)長度可以在\(|A|-lcp(A,X)_{len} \pm2\)的範圍內都試一下。
那麼我們取出\(A,B\)之後,接下來需要判斷\(A+B\)是否等於\(X\),高精度複雜度\(O(n^2)\)明顯不行,我們發現可以利用字串雜湊的方式\(O(1)\)的判斷是否合法。問題就解決了。
複雜度\(O(n)\)
(擺爛了,程式碼不寫了