比賽傳送門:https://ac.nowcoder.com/acm/contest/53366
難度適中。
? 作者:Eriktse
? 簡介:19歲,211計算機在讀,現役ACM銀牌選手?力爭以通俗易懂的方式講解演算法!❤️歡迎關注我,一起交流C++/Python演算法。(優質好文持續更新中……)?
? 閱讀原文獲得更好閱讀體驗:https://www.eriktse.com/algorithm/1109.html
A - 小d和答案修改
Tag:簽到
略。
Code:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 9;
char s[N];
signed main()
{
cin >> s + 1;
for(int i = 1; s[i]; ++ i)
{
if('a' <= s[i] && s[i] <= 'z')printf("%c", s[i] - 'a' + 'A');
else printf("%c", s[i] - 'A' + 'a');
}
return 0;
}
B - 小d和圖片壓縮
Tag:簽到
略。
Code:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e3 + 9;
int a[N][N];
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, m;cin >> n >> m;
for(int i = 1;i <= n; ++ i)
for(int j = 1;j <= m; ++ j)
cin >> a[i][j];
for(int i = 1;i <= n; i += 2)
{
for(int j = 1;j <= m;j += 2)
{
int sum = a[i][j] + a[i + 1][j] + a[i][j + 1] + a[i + 1][j + 1];
cout << sum / 4 << ' ';
}
cout << '\n';
}
return 0;
}
C - 小d和超級泡泡堂
Tag:dfs,聯通塊
給定一個大小為n x m
的地圖,求起點@
所在的聯通塊的大小。
用深度優先搜尋dfs
掃一遍即可,複雜度O(nm)
,當然你想用bfs
也行。
注意不要越界。
Code:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e3 + 9;
char mp[N][N];
bitset<N> vis[N];
int dx[] = {1, -1, 0, 0};
int dy[] = {0, 0, 1, -1};
int n, m;
int dfs(int x, int y)
{
int res = mp[x][y] == '!';
for(int i = 0;i < 4; ++ i)
{
int nx = x + dx[i], ny = y + dy[i];
if(nx < 1 || nx > n || ny < 1 || ny > m || vis[nx][ny] || mp[nx][ny] == '#')continue;
vis[nx][ny] = true;
res += dfs(nx, ny);
}
return res;
}
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> m;
for(int i = 1;i <= n; ++ i)cin >> mp[i] + 1;
int sx, sy;
for(int i = 1;i <= n; ++ i)
for(int j = 1;j <= m; ++ j)if(mp[i][j] == '@')sx = i, sy = j;
int ans = dfs(sx, sy);
cout << ans << '\n';
return 0;
}
D - 小d和孤獨的區間
Tag:思維,dp,組合計數
給定一個長度為0的01串,問有多少個子串是僅包含一個1
的。
我們可以求兩個陣列,l[i]
表示從i
點開始,往左有多少個連續的0,r[i]
表示從i
點開始,往右有多少連續的0。
然後我們列舉每一個點,如果發現a[i] == 1
,說明這個點i
可以被一些區間包含到且僅有這一個1,那麼是哪些區間呢?我們假設這個區間為[s, e]
,那麼一定有s <= i && i <= e
,且[s, i - 1]
中只包含0,[i + 1, e]
中只包含0。
那麼我們可以得到左端點s
的取值有l[i - 1] + 1
種,右端點e
的取值有r[i + 1] + 1
種。
Code:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 9;
int a[N], l[N], r[N];
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;cin >> n;
for(int i = 1;i <= n; ++ i)cin >> a[i];
for(int i = 1;i <= n; ++ i)
{
if(a[i] == 1)continue;
if(i > 1 && a[i - 1] == 0)l[i] = l[i - 1] + 1;
else l[i] = 1;
}
for(int i = n;i >= 1; -- i)
{
if(a[i] == 1)continue;
if(i < n && a[i + 1] == 0)r[i] = r[i + 1] + 1;
else r[i] = 1;
}
int ans = 0;
for(int i = 1;i <= n; ++ i)
{
if(a[i] == 1)ans += (l[i - 1] + 1) * (r[i + 1] + 1);
}
cout << ans << '\n';
return 0;
}
E - 小d的博弈
Tag:博弈,思維
給定一個大小為n x m
的矩形,Alice和Bob輪流對其進行操作,每次操作可以橫著或豎著在把矩形切一刀分成兩個長寬都為整數的矩形,然後留下面積較小的那個,兩個矩形面積相等是不被允許的,也就是說不能從中間切。
當無法繼續操作的時候就輸了。
我們分析一下容易發現幾種必敗的局面,(1, 1), (1, 2), (2, 1), (2, 2)
無法操作,直接敗。
透過分析一些特殊的矩形,比如n=m
的情況,我們可以發現n=m
的時候也是必敗的,因為下一個人一定可以模仿當前操作者的操作,從而每次都使得回到自己手上的都是一個正方形,那麼最終必然會到(1, 1)或(2, 2)
的必敗局面。
所以我們思考,當有辦法使得對方進入一個n=m
的局面,此時我們就是必勝的。
所以我們的博弈狀態為:
W必勝態: 當n > 2m || m > 2n
時,我們可以透過切分使得對手得到一個正方形,所以此時是必勝的。
其他情況,此時我肯定不能把小的再切小,因為每次切割必然使得n
或m
比原來的一半還小,就會使得對手進入W
的必勝態。所以我一定是切割n, m
中較大的那個,並且要儘可能大的切割。
Code:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 9;
void solve()
{
int n, m;cin >> n >> m;
int ans = 1;
while(1)
{
if(n > 2 * m || m > 2 * n)break;
if(n > m)n = (n - 1) / 2;
else m = (m - 1) / 2;
ans ^= 1;
}
cout << (ans ? "Alice" : "Bob") << '\n';
}
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int _;cin >> _;
while(_ --)solve();
return 0;
}
F - 小d和送外賣
Tag:樹形dp,揹包,圖論
我們將需要送外賣的點標記為need
。
定義dp狀態:
dp[x][i]
表示在以節點x
為根的子樹上刪除i
個點後可以減少的最大路程。
s[x]
表示在以節點x
為根的子樹中的需求量(標記為need
的點的個數)。
考慮一下轉移方程。
在轉移剛開始的時候,dp[x]
是不完整的,它僅包含x
這一個點的資訊,設x
的兒子分別為y1,y2,y3
,在將y1轉移給x之後,dp[x]
表示的範圍就是x點
和y1子樹
,以此類推,將y2, y3
一個個合併,最後dp[x]
表示的資訊就是以x
為根的子樹的資訊。
思考一下如何更新dp[x][k]
,我們可以將k
分解成i + (k - i)
,然後有dp[x][k] = max(dp[x][i], dp[y][k - i])
。
我們更新dp[x]
需要用到dp[x]
本身的資訊,所以我們需要開一個臨時的陣列f[]
來表示dp[x]
更新完再將f[]
複製給dp[x]
。
首先,如果s[y] == 0
,說明y子樹對答案完全沒有影響,可以直接跳過。
如果k - i == s[y]
,說明我們把y
子樹的所有需求點都刪了,那麼x -> y
這條邊可以刪除,所以對答案貢獻為2(表示最終路程可以減少2),其餘情況貢獻都為0。
更新完dp[x]
後還要更新一下s[x]
,直接加上s[y]
即可。
同時順便計算一下不刪除邊的情況下的總路程tot
,當s[y]
不為0,就必須往下走了。
Code:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 9;
int dp[N][60], s[N];//dp[i][j]表示在i為根的子樹中刪除j個點的最大貢獻
//s[i]表示以i為根的子樹中的需求量
vector<int> g[N];
bitset<N> need;
int tot, n, m;;
void dfs(int x, int p)
{
s[x] = need[x];
for(auto &y : g[x])
{
if(y == p)continue;
dfs(y, x);
if(s[y] == 0)continue;
static int f[60];
memset(f, 0, sizeof f);
for(int k = 0;k <= min(m, s[x] + s[y]); ++ k)
{
//x樹中取i個,注意此時x樹並不完整
//在y中取k - i個,此時y樹為完整的
for(int i = 0;i <= min(m, s[x]); ++ i)
{
if(k - i <= s[y] && k - i >= 0)
f[k] = max(f[k], dp[x][i] + dp[y][k - i] + (k - i == s[y] ? 2 : 0));
}
}
s[x] += s[y];
tot += 2;//此時已經保證s[y] != 0,注意看上面的continue
for(int i = 0;i <= min(m, s[x] + s[y]); ++ i)dp[x][i] = f[i];
}
}
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> m;
for(int i = 1;i < n; ++ i)
{
int x, y;cin >> x >> y;
g[x].push_back(y), g[y].push_back(x);
}
int k;cin >> k;
for(int i = 1;i <= k; ++ i)
{
int x;cin >> x;
need[x] = true;
}
dfs(1, -1);
cout << tot - dp[1][m] << '\n';
return 0;
}
? 本文由eriktse原創,創作不易,如果對您有幫助,歡迎小夥伴們點贊?、收藏⭐、留言?