Codeforces Global Round 1~3
回答\(q\)個關於\(a\)的詢問,每次詢問求:\(f(a)=max_{0<b<a} gcd(a \bigoplus b,a \& b)\), \(a<2^{25}\)
假設\(a\)有\(k\)位,對\(a\)取個反,即\(b=\overline a\)就會發現\(gcd(a \bigoplus b,a \& b)={2^k}-1\)達到最大。注意這裡的取反指的是在a的位數以內取反。但是如果\(a={2^k}-1\),因為\(b>0\)所以就不能這麼求。不妨直接暴力對每個\(k\),求解\(f(2^k-1)\),打個表出來。時間複雜度\(O(1)\)
打麻將,n種牌,每種牌\(a[i]\)個,出牌可以出三個一樣的\(\{A,A,A\}\),也可以出個順子\(\{A,A+1,A+2\}\)。求最多能出多少次牌。
首先可以敲定如果我們出了大於等於三次順子,可以轉化為出了三次三個一樣的牌。那麼不妨就枚舉出順子的次數,0,1,2三次。注意到當前牌的剩餘還跟上一張牌的順子方案有關,那麼還得記錄上一張牌的順子狀態。
設\(dp[i][x][y]\)表示第\(i\)張牌出了\(x\)次\(\{i,i+1,i+2\}\),出了\(y\)次\(\{i-1,i,i+1\}\),列舉上一次的狀態然後暴力轉移,最後牌\(i\)剩餘下來的牌都拿來出三個一樣的。
有N堆石頭,每堆是\(c[i]\)個,每一次操作可以將某個\(c[i]\)變成\(c[i-1]+c[i+1]-c[i]\),問最終是不是能變成陣列\(t\)
神祕構造題。一次操作相當於交換了差分陣列相鄰兩項...求一下c陣列的差分陣列、t陣列的差分陣列,排個序看看是不是一樣的就行了
把一棵有根樹,根節點是1,按dfs序拍平,每次詢問問dfs序在區間\([l,r]\)內的葉子到某節點\(u\)的最短距離。1不算葉子
題目明示dfs序拍平。考慮節點\(u\)到葉子節點的距離,發現分為兩種,一種是\(u\)子樹內的葉子結點稱之為\(A\),一種是\(u\)子樹外的葉子節點稱之為\(B\)
所以先求根節點到葉子節點的距離,存線上段樹裡,然後dfs一遍樹,每dfs到一個節點就線上段樹上利用區間修改去遞推。時間複雜度\(O(n log_2{n})\)
順便測了一下我的線段樹模板...
點選檢視程式碼
void dfs(int u, int fa, int &stamp)
{
dfn[u] = ++stamp, size[u] = 1;
for (auto [v, w] : G[u])
if (v != fa)
{
dist[v] = dist[u] + w;
dfs(v, u, stamp);
size[u] += size[v];
}
init[dfn[u]].mn = (size[u] == 1) ? dist[u] : 1e18;
}
void dfs(int u, int fa, SegmentTree &Tree)
{
for (int x : Q[u])
qry[x].ans = Tree.query(qry[x].l, qry[x].r);
for (auto [v, w] : G[u])
if (v != fa)
{
Tag delta[2] = {-w, w};
Tree.apply(dfn[v], dfn[v] + size[v] - 1, delta[0]);
if (dfn[v] - 1 >= 1)
Tree.apply(1, dfn[v] - 1, delta[1]);
if (size[1] >= dfn[v] + size[v])
Tree.apply(dfn[v] + size[v], size[1], delta[1]);
dfs(v, u, Tree);
Tree.apply(dfn[v], dfn[v] + size[v] - 1, delta[1]);
if (dfn[v] - 1 >= 1)
Tree.apply(1, dfn[v] - 1, delta[0]);
if (size[1] >= dfn[v] + size[v])
Tree.apply(dfn[v] + size[v], size[1], delta[0]);
}
}
在樹上玩井字棋遊戲。白先手,有一些點已經塗成白色了,問是白贏還是黑贏。
首先井字棋先手必勝,後手求和。然後還有一些點已經塗成白色了,黑色真招誰惹誰了...
不如先考慮空樹的情況吧。
首先如果有個節點的度數大於3,那麼白色必勝。
白色下1號點,黑色必須去堵一邊。白色再下另一邊,還至少剩下兩邊,黑色只能堵一邊,所以白色必勝。
接下來就可以考慮節點度數為3、為2的情況。
一個度數為3的節點,如果有兩個子節點不是葉子節點。那麼白色肯定也必勝。
白色先手下1號,接下來無論黑色下在哪,白色長度為2的鏈都有兩個方向可以擴充成長度為3的鏈
如果所有節點度數為2(除葉子節點),顯然是一個平手。
所以重點還是在度數為3、連著兩個葉子節點的點。
假設有且僅有兩個這樣的點。
這種情況下,白色下1號點,黑色只能下在6或者2,那麼白色往反方向擴充,就會出現一個有兩個方向可擴充的長度為2的鏈,白色必勝。
而這種情況下無論怎樣都不會出現兩個方向可擴充的長度為2的鏈,因此是平手。
所以問題在於是否在度為3的節點處能出現長度為2的鏈。當白色下在一個點時,黑色要堵一邊。兩個度數為3的節點\(u,v\),白色先下在\(u->v\)鏈上離\(u\)最近的點,黑色必然要堵住\(u\)一邊。那麼白色選擇隔一個位置往\(v\)的方向下,黑色必須要堵中間。這樣黑白交替著,如果白色能下到節點\(v\)前面的那個點,那麼白色下一步下在\(v\)就又會形成一個長度為2的兩個方向可走的鏈。假設下了\(k\)手,那麼\(u,v\)的距離就是\(2k+1\)(算上\(u,v\)兩個點)。因此\(u,v\)距離為奇數,白色勝,否則平手。
接下來考慮有3個度為3的節點\(u,v,w\)。假設是\(w\)在\(u,v\)鏈上。那麼\(dist[u][v] = dist[u][w] + dist[w][v]-1\),如果\(dist[u][w]\)和\(dist[w][v]\)都是偶數,那麼\(dist[u][v]\)是奇數,先手必勝;反之則也有距離也奇數的兩個度為3的節點,因此先手必勝。
所以當度為3的節點大於3個的時候,先手必勝。
所以總結一下,有度數大於3的節點,先手必勝;度數等於3的節點,只有一個相鄰的葉子節點,先手必勝;度數等於3且有兩個相鄰的葉子節點的點,如果有大於2個,先手必勝,如果有2個且距離為奇數,先手必勝。剩下的情況都是平局。
那麼考慮如果預先放置了白色節點怎麼做。可以等價於一個空的樹,要在一個節點上放白色,那麼黑色必然要放在一個位置去堵他,把這個黑色的點刨去就是我們面臨的原樹了。要構造這樣的case,就要用到度數等於3的那種情況。把這個已經塗成了白色的點置空,再連一個度數為3、連著兩個葉子節點的點。白色肯定喜歡下這樣的節點,而且黑色必然去堵上這個度數為3的節點,連著的兩個葉子,白色也不會去考慮它們,對原樹不會造成影響。
這樣改造完之後就成為空樹的井字棋問題了。實現一下就好了。空間開4倍。
點選檢視程式碼
void Main()
{
int n;
scanf("%d", &n);
for (int i = 1; i < n; ++i)
{
int u, v;
scanf("%d%d", &u, &v);
G[u].push_back(v), G[v].push_back(u);
deg[u]++, deg[v]++;
}
scanf("%s", str + 1);
int m = n;
for (int i = 1; i <= n; ++i)
if (str[i] == 'W')
{
G[i].push_back(m + 1), G[m + 1].push_back(i);
G[m + 1].push_back(m + 2), G[m + 2].push_back(m + 1);
G[m + 1].push_back(m + 3), G[m + 3].push_back(m + 1);
deg[i]++, deg[m + 1] += 3, deg[m + 2]++, deg[m + 3]++;
m += 3;
}
std::function<void()> CLEAR = [m]()
{
for (int i = 1; i <= m; ++i)
G[i].clear(), deg[i] = 0;
};
for (int i = 1; i <= m; ++i)
if (deg[i] >= 4)
return printf("White\n"), CLEAR();
int vert[3] = {0, 0};
for (int i = 1, size = 0; i <= m; ++i)
if (deg[i] >= 3)
{
++size;
if (size < 3)
vert[size] = i;
else
return printf("White\n"), CLEAR();
}
for (int i = 1; i <= 2; ++i)
if (int u = vert[i])
{
int size = 0;
for (int v : G[u])
if (deg[v] >= 2)
size++;
if (size >= 2)
return printf("White\n"), CLEAR();
}
std::function<bool(int, int, int, int)> dfs = [&](int u, int fa, int len, int target) -> bool
{
if (u == target)
return len % 2 == 0;
bool res = false;
for (int v : G[u])
if (v != fa)
res |= dfs(v, u, len + 1, target);
return res;
};
if (vert[1] && vert[2])
if (dfs(vert[1], 0, 0, vert[2]))
return printf("White\n"), CLEAR();
printf("Draw\n"), CLEAR();
}
構造一個長度為\(n\)的數字串,使得出現的數字子串的值在\([l,r]\)內的子串最多。\(n \le 2000, l,r \le 10^{800}\)
感覺上是個AC自動機,把\([l,r]\)的數字都扔進AC自動機裡,然後跑個動態規劃。
然後因為狀態太多就成為了TLE自動機...
這個TLE自動機裡會出現大量的滿十叉樹。
滿十叉樹就是所有可能的狀態都會出現,那麼對於這裡面每一個終止節點,它們的權值都是相同的,沒有必要去走遍所有節點。當走到一個滿十叉樹的根的時候,就可以提前算出對於該滿十叉樹一個基礎權值base,後面無論跟什麼都會有base的權值,然後跳出這個滿十叉樹去別的節點裡獲取更多特殊權值。
那麼這些滿十叉樹都怎麼識別呢?就要用到數位DP的思想了。數位DP裡面把\(r\)按每位決策分成一棵樹,其中,脫離了\(r\)數位限制的就可以隨意取值,也就是滿十叉樹了。那麼就可以把r扔進去,按數位DP決策那棵樹構造Trie,每個葉子權值為1,再把\(l-1\)扔進去,按數位DP決策樹構造Trie,權值賦為-1。
接下來考慮dp。
設\(dp[i][u]\)表示長度為i,當前節點為u的最大權值。一般來說dp形式應該是\(dp[i][u] = dp[i-1][v] + val[u]\)。然而這裡面有滿十叉樹的情況下,要跳過當前十叉樹,就得把未來可能所有的長度都算進去,也就是一個\(g[u][k]\),表示在u節點,往前走k步得到的權值。這個\(g[u][k]\)就要靠AC自動機構建來求啦。那麼dp方程就是\(dp[i][u] = dp[i-1][v]+\sum_{k=0}^{n-i}g[u][k]\). 後面是個字首和,預處理一下就行了。
最後dfs一個方案就行了。實現時我把\(g[u][k]\)裡的u按數位DP決策樹父節點的決策換了一種表達,這樣AC自動機狀態就少了一點。
點選檢視程式碼
const int MAXN = 2000, MAXR = 800;
struct state
{
int trans[10], fail;
} acam[MAXR * 2 + 5];
#define trans(u, x) acam[u].trans[x]
#define fail(u) acam[u].fail
int cnt = 0, base[MAXR * 2 + 5][MAXN + 5][10];
void insert(char *s, int val)
{
int u = 0, len = strlen(s);
for (int i = 0; i < len; ++i)
{
int x = s[i] - '0';
for (int j = (u == 0); j < x; ++j)
base[u][len - i][j] += val;
if (i == len - 1)
base[u][len - i][x] += val;
if (!trans(u, x))
trans(u, x) = ++cnt;
u = trans(u, x);
}
}
void build()
{
std::queue<int> q;
for (int i = 0; i < 10; ++i)
if (trans(0, i))
q.push(trans(0, i));
while (!q.empty())
{
int u = q.front();
q.pop();
for (int x = 0; x < 10; ++x)
if (int &v = trans(u, x))
q.push(v), fail(v) = trans(fail(u), x);
else
v = trans(fail(u), x);
}
}
char L[MAXR + 5], R[MAXR + 5];
int n;
bool vis[MAXN + 5][MAXR * 2 + 5];
ll dp[MAXN + 5][MAXR * 2 + 5];
void Main()
{
scanf("%s%s", L + 1, R + 1);
scanf("%d", &n);
int l = strlen(L + 1), r = strlen(R + 1);
for (int i = l; i >= 1; --i)
{
if (L[i] == '0')
L[i] = '9';
else
{
L[i]--;
break;
}
}
if (L[1] == '0')
{
for (int i = 1; i <= l; ++i)
L[i] = L[i + 1];
L[l--] = 0;
}
if (l)
insert(L + 1, -1);
insert(R + 1, 1);
build();
for (int i = std::max(1, l); i < r; ++i)
for (int j = 1; j < 10; ++j)
base[0][i][j]++;
for (int i = 1; i <= cnt; ++i)
for (int j = 1; j <= n; ++j)
for (int k = 0; k <= 9; ++k)
base[i][j][k] += base[fail(i)][j][k];
for (int i = 0; i <= cnt; ++i)
for (int j = 2; j <= n; ++j)
for (int k = 0; k <= 9; ++k)
base[i][j][k] += base[i][j - 1][k];
memset(dp, 0xcf, sizeof(dp));
dp[0][0] = 0;
for (int i = 0; i < n; ++i)
for (int u = 0; u <= cnt; ++u)
{
if (dp[i][u] < 0)
continue;
for (int k = 0; k <= 9; ++k)
{
int v = trans(u, k);
dp[i + 1][v] = std::max(dp[i + 1][v], dp[i][u] + base[u][n - i][k]);
}
}
// for (int i = 0; i <= n; ++i)
// for (int u = 0; u <= cnt; ++u)
// printf("dp[%d][%d] = %lld\n", i, u, dp[i][u]);
ll ans = 0;
for (int u = 0; u <= cnt; ++u)
if (dp[n][u] > ans)
ans = dp[n][u];
printf("%lld\n", ans);
std::vector<int> best;
std::function<bool(int, int)> dfs = [&](int i, int u) -> bool
{
if (i == n && dp[i][u] == ans)
return true;
if (vis[i][u])
return false;
vis[i][u] = true;
for (int k = 0; k <= 9; ++k)
{
int v = trans(u, k);
if (dp[i + 1][v] == dp[i][u] + base[u][n - i][k])
if (dfs(i + 1, v))
return best.push_back(k), true;
}
return false;
};
dfs(0, 0);
for (int i = best.size() - 1; i >= 0; --i)
printf("%d", best[i]);
printf("\n");
}
GlobalRound2:什麼牛馬構造題專場……
未完待續
CF1119C
CF1119D
CF1119E
CF1119F
CF1119G
CF1119H
CF1148C
CF1148D
CF1148E
CF1148F
CF1148G
CF1148H