先聲明一下,如果你是初學者,你可能會看不懂其中的一些東西,原因是你的知識點以及技巧沒有跟上,我會盡量寫得詳細一點,如果還有不懂,歡迎留言。 玩具謎題 toy知識點模擬 分析可以發(fā)現(xiàn),本題就是根據(jù)要求在環(huán)上順時針或者逆時針走動,那么假設(shè)當前的位置是 ,那么逆時針走 個到達的位置是 ,順時針走 個到達的位置是 。模擬每一次的指令,判斷每次是走順時針還是逆時針即可。 實現(xiàn)上要注意,C 中對于模運算的定義與數(shù)學中的模運算是不同的,我們知道在數(shù)學上 ,問題就在于 的計算上。在大部分編譯器中,兩個 int 相除,是直接丟棄小數(shù)位得到答案的,這就導致了在 C 中兩個 int 相除并非總是得到 (例如 ),所以 為負數(shù)的情況下,取模會存在問題, 不一定能夠得到一個非負整數(shù),但是我們可以保證這個數(shù)在 內(nèi),在所以實際需要取模的時候,采用 來得到非負整數(shù)。 代碼#include <cstdio>
using namespace std;
const int MAX_N = 100000 3, MAX_LEN = 10 3;
int n, m, dir[MAX_N];
char str[MAX_N][MAX_LEN];
int main() {
scanf('%d%d', &n, &m);
for (int i = 0; i < n; i)
scanf('%d%s', dir i, str[i]);
int pos = 0;
for (int i = 0, d, a; i < m; i) {
scanf('%d%d', &d, &a);
int dd = d ^ dir[pos];
if (dd == 1) (pos = a) %= n;
else (pos -= a) %= n;
pos = (pos % n n) % n;
}
printf('%s\n', str[pos]);
return 0;
}
天天愛跑步 running考點DFS,LCA(最近公共祖先),差分標記,前綴和 分析對于 NOIP 初學者來說,這個題在考場上是比較沒有辦法 A 掉的,但是這并不妨礙我們拿到暴力分。 25 分對于前 5 個測試點,。 可以考慮對于每一個人 ,都使用 BFS 找出 的路徑,然后更新路徑上有滿足條件的點即可。至于找出路徑的方法,只需要從 開始 BFS,并且在 BFS 中從 擴展到 時,記錄 的前繼為 ,最后我們從 開始,不斷地找前繼,最終到 ,這樣就還原出了路徑,你可能還需要記錄一個到點 的距離來幫助判斷路徑上的點是否滿足條件。 復雜度:。 40 分接下來 3 個測試點,,形成一條鏈。 本題有 2s 的時限,大概能夠承受 的運算量,如果使用 25 分算法,運算量將達到 ,絕對無法承受。所以我們不能采用還原路徑的方法。 由于鏈上的點依次是 ,而題目中的路徑有從編號小的點走到編號大的點或者從編號大的點走到編號小的點。我們將這兩種路徑分開考慮,先考慮從編號小的點走到編號大的點 。 現(xiàn)在問題轉(zhuǎn)化成了支持兩種操作:
我們考慮使用差分化標記實現(xiàn),這個思想在 NOIP 2012 借教室 出現(xiàn)過。 對于反過來的路徑 ,同理可以發(fā)現(xiàn)點 答案 1 的充分必要條件是 ,設(shè) ,那么條件轉(zhuǎn)化為 ,也是一個常數(shù),可以用類似的方法處理。 復雜度:。這個做法比較重要,對于初學者建議實現(xiàn)這個程序。 樹鏈剖分剛剛我們得出了鏈上的算法,樹鏈剖分可以讓我們將樹剖分成鏈,這樣樹上的問題就可以轉(zhuǎn)化為鏈上的問題,套用鏈上的算法即可。每條路徑產(chǎn)生 個標記,處理每條路徑的復雜度也是 ,最后結(jié)算標記的復雜度 ,所以總復雜度 。依據(jù)實現(xiàn)的情況,得到 95 或 100 分。 100 分NOIP 顯然不會考樹鏈剖分,這題有更優(yōu)美的解法。我們首先求出每條路徑的 LCA,使用 Tarjan 算法 在 的時間內(nèi)預處理出 LCA。對于每一條路徑,我們拆成直上直下的兩條鏈(如果本身就是這種鏈就不拆),設(shè) 為 點的深度。我們考慮鏈 ,對于向下的鏈,路徑上滿足條件的點 必須滿足 ,即 是一個定值。對于向上的鏈,有 ,即 是一個定值,問題同樣轉(zhuǎn)化為了支持兩種操作(對于兩種鏈分別考慮,這里只說了第一種鏈):
如果在路徑 上插入一個數(shù) ,可以差分轉(zhuǎn)化為在 這個地方插入一個 ,在 這個地方刪除一個數(shù) ,則點 上的數(shù)就是其子樹的和(一個常用技巧,建議畫圖驗證)。 顯然對于每一個點都求一次子樹是 的,考慮如何快速計算。我們可以弄出樹的 DFS 序,對于一棵子樹,它在 DFS 序中必然是連續(xù)的一段。那么對于一個點 ,我們只需要查詢它的子樹代表的一段連續(xù)的區(qū)間中有多少個數(shù) 。假設(shè)其子樹的區(qū)間為 , 為 中有多少個 ,那么 的答案等于 。我們先算出所有點的 和 ,然后離線,順序遍歷 DFS 序列來結(jié)算每個前綴和。這一步比較抽象,大體的思想就是每遍歷到一個位置 ,結(jié)算所有包含 項的區(qū)間,由于只有 個區(qū)間,所以結(jié)算的復雜度是 的。 具體的實現(xiàn)見代碼,復雜度:。 代碼樹鏈剖分// Created by Sengxian on 2016/11/23.
// Copyright (c) 2016年 Sengxian. All rights reserved.
#include <bits/stdc .h>
using namespace std;
const int MAX_N = 300000 3;
struct edge {
edge *next;
int to;
edge(edge *next = NULL, int to = 0): next(next), to(to) {}
} pool[MAX_N * 2], *pit = pool, *first[MAX_N];
int n, m, w[MAX_N], ans[MAX_N];
int dfn[MAX_N], fa[MAX_N], s[MAX_N], bel[MAX_N], dep[MAX_N], chainDep[MAX_N];
void dfs1(int u, int f) {
fa[u] = f, s[u] = 1;
for (edge *e = first[u]; e; e = e->next) if (e->to != f) {
dep[e->to] = dep[u] 1;
dfs1(e->to, u);
s[u] = s[e->to];
}
}
vector<int> chains[MAX_N];
void dfs2(int u, int num) {
static int tsp = 0;
dfn[u] = tsp , bel[u] = num;
chains[num].push_back(u);
int mx = -1, id = 0;
for (edge *e = first[u]; e; e = e->next)
if (e->to != fa[u] && s[e->to] > mx) mx = s[id = e->to];
if (mx == -1) return;
chainDep[id] = chainDep[u] 1;
dfs2(id, num);
for (edge *e = first[u]; e; e = e->next)
if (e->to != fa[u] && e->to != id) chainDep[e->to] = 0, dfs2(e->to, e->to);
}
typedef pair<int, int> state;
vector<state> mark1[MAX_N], mark2[MAX_N];
#define sz(x) ((int)x.size())
int dis(int u, int v) {
int d = 0;
while (bel[u] != bel[v]) {
if (dep[bel[u]] < dep[bel[v]]) swap(u, v);
d = chainDep[u] 1;
u = fa[bel[u]];
}
if (dep[v] < dep[u]) swap(u, v);
d = chainDep[v] - chainDep[u] 1;
return d - 1;
}
void solve(int u, int v) {
int disU = 0, disV = dis(u, v), d, t;
while (bel[u] != bel[v]) {
if (dep[bel[u]] > dep[bel[v]]) {
mark1[u].push_back(state(disU - (sz(chains[bel[u]]) - 1 - chainDep[u]), 1));
disU = chainDep[u] 1;
u = fa[bel[u]];
} else {
d = disV - chainDep[v];
mark2[bel[v]].push_back(state(d, 1));
if (chainDep[v] 1 != sz(chains[bel[v]])) {
t = chains[bel[v]][chainDep[v] 1];
mark2[t].push_back(state(d, -1));
}
disV -= chainDep[v] 1;
v = fa[bel[v]];
}
}
if (dep[u] < dep[v]) {
mark2[u].push_back(state(disU - chainDep[u], 1));
if (chainDep[v] 1 != sz(chains[bel[v]])) {
t = chains[bel[v]][chainDep[v] 1];
mark2[t].push_back(state(disU - chainDep[u], -1));
}
} else {
mark1[u].push_back(state(disU - (sz(chains[bel[u]]) - 1 - chainDep[u]), 1));
if (v != bel[v]) {
mark1[fa[v]].push_back(state(disU - (sz(chains[bel[u]]) - 1 - chainDep[u]), -1));
}
}
}
static int cnt[MAX_N * 2], ti[MAX_N * 2];
int now_t = 0;
inline int get(int x) {
if (ti[x] != now_t) ti[x] = now_t, cnt[x] = 0;
return cnt[x];
}
inline void add(int x, int v) {
if (ti[x] != now_t) ti[x] = now_t, cnt[x] = 0;
cnt[x] = v;
}
void cal() {
for (int tt = 0; tt < n; tt) if (bel[tt] == tt) {
int len = chains[tt].size();
now_t ;
for (int i = 0; i < len; i) {
for (int j = 0; j < (int)mark2[chains[tt][i]].size(); j)
add(mark2[chains[tt][i]][j].first n, mark2[chains[tt][i]][j].second);
ans[chains[tt][i]] = get(w[chains[tt][i]] - i n);
}
now_t ;
for (int i = len - 1; i >= 0; --i) {
for (int j = 0; j < (int)mark1[chains[tt][i]].size(); j) {
add(mark1[chains[tt][i]][j].first n, mark1[chains[tt][i]][j].second);
}
ans[chains[tt][i]] = get(w[chains[tt][i]] - (len - 1 - i) n);
}
}
}
int main() {
scanf('%d%d', &n, &m);
for (int i = 0, u, v; i < n - 1; i) {
scanf('%d%d', &u, &v), --u, --v;
first[u] = new (pit ) edge(first[u], v);
first[v] = new (pit ) edge(first[v], u);
}
for (int i = 0; i < n; i) scanf('%d', w i);
dfs1(0, -1), dfs2(0, 0);
for (int i = 0, u, v; i < m; i) {
scanf('%d%d', &u, &v), u--, v--;
solve(u, v);
}
cal();
for (int i = 0; i < n; i) printf('%d%c', ans[i], i 1 == n ? '\n' : ' ');
return 0;
}
正解// Created by Sengxian on 2016/11/23.
// Copyright (c) 2016年 Sengxian. All rights reserved.
#include <bits/stdc .h>
using namespace std;
typedef long long ll;
inline int readInt() {
static int n, ch;
n = 0, ch = getchar();
while (!isdigit(ch)) ch = getchar();
while (isdigit(ch)) n = n * 10 ch - '0', ch = getchar();
return n;
}
const int MAX_N = 300000 3, MAX_M = 300000 3;
struct edge {
edge *next;
int to;
edge(edge *next = NULL, int to = 0): next(next), to(to) {}
} pool[(MAX_N MAX_M) * 2], *pit = pool, *first[MAX_N], *qFirst[MAX_N];
int n, m, w[MAX_N], w1[MAX_N], w2[MAX_N];
int queryU[MAX_M], queryV[MAX_M], queryLCA[MAX_N], queryAns[MAX_M];
int fa[MAX_N], s[MAX_N], d[MAX_N], dfn[MAX_N], seq[MAX_N];
namespace disjoint_union {
int ufa[MAX_N];
inline void init(int n) {
for (int i = 0; i < n; i) ufa[i] = i;
}
inline int find(int x) {
return ufa[x] == x ? x : ufa[x] = find(ufa[x]);
}
inline void unite(int x, int y) {
x = find(x), y = find(y);
ufa[x] = y;
}
}
using namespace disjoint_union;
void dfs(int u, int fa) {
static bool vis[MAX_N];
static int tsp = 0;
vis[u] = true, ::fa[u] = fa, s[u] = 1;
dfn[u] = tsp, seq[tsp ] = u;
for (edge *e = first[u]; e; e = e->next) if (e->to != fa) {
d[e->to] = d[u] 1;
dfs(e->to, u);
unite(e->to, u);
s[u] = s[e->to];
}
for (edge *q = qFirst[u]; q; q = q->next) {
int v = queryU[q->to] == u ? queryV[q->to] : queryU[q->to];
if (vis[v]) queryLCA[q->to] = find(v);
}
}
typedef pair<int, int> state;
vector<state> mark1[MAX_N], mark2[MAX_N], pos[MAX_N];
int cnt1[MAX_N * 4], cnt2[MAX_N * 4];
inline void giveTag(int u, int v, int s, bool flag = false) {
if (d[u] <= d[v]) {
mark1[v].push_back(state(s - d[u], 1));
if (fa[u] >= 0) mark1[fa[u]].push_back(state(s - d[u], -1));
} else {
mark2[u].push_back(state(s d[u], 1));
if (flag) mark2[v].push_back(state(s d[u], -1));
else if (fa[v] >= 0) mark2[fa[v]].push_back(state(s d[u], -1));
}
}
void solve() {
for (int i = 0; i < n; i) w1[i] = w[i] - d[i];
for (int i = 0; i < n; i) w2[i] = w[i] d[i];
for (int i = 0, u, v, LCA; i < m; i) {
u = queryU[i], v = queryV[i], LCA = queryLCA[i];
if (u == LCA || v == LCA) giveTag(u, v, 0);
else giveTag(u, LCA, 0, true), giveTag(LCA, v, d[u] - d[LCA]);
}
for (int i = 0; i < n; i) {
pos[dfn[i]].push_back(state(i, -1));
pos[dfn[i] s[i]].push_back(state(i, 1));
}
int del = n * 2;
for (int i = 0, u; i < n; i) {
u = seq[i];
for (int j = 0; j < (int)mark1[u].size(); j)
cnt1[mark1[u][j].first del] = mark1[u][j].second;
for (int j = 0; j < (int)mark2[u].size(); j)
cnt2[mark2[u][j].first del] = mark2[u][j].second;
for (int j = 0; j < (int)pos[i 1].size(); j) {
state st = pos[i 1][j];
queryAns[st.first] = cnt1[w1[st.first] del] * st.second;
queryAns[st.first] = cnt2[w2[st.first] del] * st.second;
}
}
}
int main() {
n = readInt(), m = readInt();
for (int i = 0, u, v; i < n - 1; i) {
u = readInt() - 1, v = readInt() - 1;
first[u] = new (pit ) edge(first[u], v);
first[v] = new (pit ) edge(first[v], u);
}
for (int i = 0; i < n; i) w[i] = readInt();
for (int i = 0; i < m; i) {
queryU[i] = readInt() - 1, queryV[i] = readInt() - 1;
qFirst[queryU[i]] = new (pit ) edge(qFirst[queryU[i]], i);
qFirst[queryV[i]] = new (pit ) edge(qFirst[queryV[i]], i);
}
init(n), dfs(0, -1);
solve();
for (int i = 0; i < n; i)
printf('%d%c', queryAns[i], i 1 == n ? '\n' : ' ');
return 0;
}
吐槽考試的時候?qū)懥藗€樹鏈剖分,考完以后寫了個線性做法,mdzz 跑的時間居然一樣。 換教室 classroom知識點數(shù)學期望,動態(tài)規(guī)劃,最短路 分析首先有 ,使用 floyd 算法求出兩兩點對之間的最短路。接著可以發(fā)現(xiàn),這是一個經(jīng)典的序列 DP 的模型,每個點有選或者不選兩種決策,那么根據(jù)期望的線性性,容易發(fā)現(xiàn)每次 DP 只與最后兩個選不選有關(guān),考慮 為到第 個點,已經(jīng)選了 個, 表示第 個點是否選擇換教室,那么轉(zhuǎn)移就是根據(jù)后兩個是否選擇更換,來分情況討論(設(shè) 為更換成功的概率, 為不換的教室, 為換的教室, 為 與 之間的最短路): 如果點 不選,那么根據(jù)上一個選不選來計算期望(期望的線性告訴我們可以這樣做)。 如果點 選,也是根據(jù)上一個選不選來計算期望,第二個式子會比較麻煩,有四種情況。 代碼// Created by Sengxian on 2016/11/23.
// Copyright (c) 2016年 Sengxian. All rights reserved.
#include <bits/stdc .h>
using namespace std;
typedef long long ll;
inline int readInt() {
static int n, ch;
n = 0, ch = getchar();
while (!isdigit(ch)) ch = getchar();
while (isdigit(ch)) n = n * 10 ch - '0', ch = getchar();
return n;
}
const int MAX_N = 2000 3, MAX_M = 2000 3, MAX_V = 300 3;
int n, m, v, e, c[MAX_N], d[MAX_N];
double p[MAX_N];
int G[MAX_V][MAX_V];
void floyd() {
for (int k = 0; k < v; k)
for (int i = 0; i < v; i)
for (int j = 0; j < v; j)
G[i][j] = min(G[i][j], G[i][k] G[k][j]);
}
double dp[MAX_N][MAX_M][2];
void solve() {
for (int i = 0; i < n; i)
for (int j = 0; j <= m; j)
dp[i][j][0] = dp[i][j][1] = 1e30;
dp[0][0][0] = 0.0, dp[0][1][1] = 0.0;
for (int i = 1; i < n; i)
for (int j = 0; j <= m; j) {
dp[i][j][0] = min(dp[i - 1][j][0] G[c[i - 1]][c[i]], dp[i - 1][j][1] p[i - 1] * G[d[i - 1]][c[i]]
(1 - p[i - 1]) * G[c[i - 1]][c[i]]);
if (j) dp[i][j][1] = min(dp[i - 1][j - 1][0] p[i] * G[c[i - 1]][d[i]] (1 - p[i]) * G[c[i - 1]][c[i]],
dp[i - 1][j - 1][1] p[i] * p[i - 1] * G[d[i - 1]][d[i]] (1 - p[i]) * p[i - 1] * G[d[i - 1]][c[i]]
p[i] * (1 - p[i - 1]) * G[c[i - 1]][d[i]] (1 - p[i]) * (1 - p[i - 1]) * G[c[i - 1]][c[i]]);
}
double ans = 1e30;
for (int i = 0; i <= m; i) ans = min(ans, min(dp[n - 1][i][0], dp[n - 1][i][1]));
printf('%.2f\n', ans);
}
int main() {
n = readInt(), m = readInt(), v = readInt(), e = readInt();
for (int i = 0; i < n; i) c[i] = readInt() - 1;
for (int i = 0; i < n; i) d[i] = readInt() - 1;
for (int i = 0; i < n; i) scanf('%lf', p i);
memset(G, 0x3f, sizeof G);
for (int i = 0, f, t, c; i < e; i) {
f = readInt() - 1, t = readInt() - 1, c = readInt();
G[f][t] = G[t][f] = min(G[f][t], c);
}
for (int i = 0; i < v; i) G[i][i] = 0;
floyd();
solve();
return 0;
}
總結(jié)Day 1 的題還是有一定難度的,第二題是一個不錯的題,放在這個位置有不小的震懾力,而且可以意外地讓一批偽高手栽下跟頭——想不出來正解,也打不出高級數(shù)據(jù)結(jié)構(gòu),結(jié)果爆 0。第三題如果熟悉期望 DP 的話,是一道比較簡單的題,可見此題的目的主要還是用于普及一下數(shù)學期望,以便在后續(xù)比賽中出現(xiàn)。 |
|