# DDJ Regular Contest Round#2 Toturial --- ## A. 果然我的高一平凡生活搞錯了。 `tag`:`Math, DP` 本題兩種想法: - $DP$ 那麼這邊就是實作巴斯卡三角形的 DP 法: $Base$ $Case$:`c[n][0] = c[n][n] = 1` `c[n][i] = c[n - 1][i] + c[n - 1][i - 1]`,$0 < i < n$。 :::spoiler `code` ```cpp= #include<bits/stdc++.h> #define LL long long #define endl '\n' using namespace std; int main() { cin.tie(nullptr); ios_base::sync_with_stdio(false); int T, n; unsigned LL ans[63][63]; memset(ans, 0, sizeof(ans)); for (int x = 1; x <= 62; x++) ans[x][0] = 1; ans[1][1] = 1; for (int x = 2; x <= 62; x++) { for (int y = 1; y <= x; y++) { ans[x][y] = ans[x - 1][y - 1] + ans[x - 1][y]; } } cin >> T; while (T--) { cin >> n; for (int x = 0; x <= n; x++) cout << ans[n][x] << ' '; cout << '\n'; } return 0; } ``` ::: - 題目其實寫得很清楚:對於每個 $n$,依序輸出 $C^n_i$ 且 $C^n_i$ = $C^n_{i - 1}\times \frac{n - i + 1}{i}$ :::spoiler `code` ```cpp= #include<bits/stdc++.h> #define LL long long #define endl '\n' using namespace std; int main() { cin.tie(nullptr); ios_base::sync_with_stdio(false); int T, n; unsigned LL ans[63][63]; for (int x = 1; x < 63; x++) for (int y = 0; y < 63; y++) ans[x][y] = 1; for (int x = 1; x <= 62; x++) { for (int y = 1; y <= x; y++) { ans[x][y] = ans[x][y - 1] * (x - y + 1) / y; } } cin >> T; while (T--) { cin >> n; for (int x = 0; x <= n; x++) cout << ans[n][x] << ' '; cout << '\n'; } return 0; } ``` ::: ## B. 果然我的高一平凡生活搞錯了。續 `tag`:`Math, Modulo inverse` 本題因為與 $A.$ 相比, $n$ 明顯大得多,可以看出剛剛 $DP$ 空間複雜度是是 $O(\frac{{n_{max}} ^2}{2})$, 這題顯然不能這樣做,因為 $n_{max} = 2\times 10^4$。 當然這複雜度可以壓成 $O(2n_{max})$,不過如此時間複雜度將變為 $O(TN^2)$,這樣太慢。 因此這裡只剩下剛剛 $A.$ 的 $C^n_i$,因為還要 $mod$ $10^9 + 7$,所以需要用到模逆元。 又 $10^9 + 7$ 為質數,所以模逆元求法適用費馬小定理證明: 對於任一小於質數 $p$ 的數 $n$,$n$ 對 $p$ 的模逆元為:$n^{p - 2}$ $mod$ $p$, 那次方的部分可以用快速冪優化。 :::spoiler `code` ```cpp= #include<bits/stdc++.h> #define LL long long #define endl '\n' using namespace std; constexpr int m = 1e9 + 7; LL power(int n) { LL ret = 1, tmp = n; for (int k = m - 2; k; tmp = tmp * tmp % m, k >>= 1) if (k & 1) ret = ret * tmp % m; return ret; } int main() { cin.tie(nullptr); ios_base::sync_with_stdio(false); int n, T; cin >> T; LL pow[20000]; for (int x = 0; x < 20000; x++) pow[x] = power(x + 1); while (T--) { LL ans = 1; cin >> n; cout << ans << ' '; for (int x = n; x; x--) { ans = ans * x % m, ans = ans * pow[n - x] % m; cout << ans << ' '; } cout << '\n'; } return 0; } ``` ::: ## C. The Greedy Mouse `tag`: `DP`、`Knapsack` 可以想到是 $DP[N][C]$, 而且是對 $prefix$ 做 $DP$。 只要想到這個, 轉移式就很直觀了。 $$DP[i][j]=max(DP[i-1][k-c[i-1][j]]+prefix[i-1][j], DP[i][k], DP[i-1][k])$$ $i$ 代表 $N$ 的變量,由 $1$ 到 $N$ $j$ 代表 $M$ 的變量,由 $1$ 到 $M$ $k$ 代表 $C$ 的變量,由 $C$ 到 $0$ :::spoiler `Solution` ```cpp= #include <iostream> #include <vector> #include <string.h> using namespace std; const int mxC = 2500, mxN = 50; int dp[mxN+1][mxC+1]; void solve() { int N, M, C; cin >> N >> M >> C; vector<vector<int>> v, c; for(int i = 0; i < N; ++i) { int sum = 0; v.push_back(vector<int>(0)), c.push_back(vector<int>(0)); for(int j = 0; j < M; ++j) { int t; cin >> t; sum += t; v[i].push_back(sum), c[i].push_back(j+1); } } memset(dp, 0, sizeof(dp)); for(int i = 0; i < N; ++i) { for(int j = 0; j < M; ++j) { for(int k = C; k-c[i][j] >= 0; --k) dp[i+1][k] = max({dp[i][k-c[i][j]]+v[i][j], dp[i+1][k], dp[i][k]}); } } cout << dp[N][C] << '\n'; } int main() { cin.tie(0), ios_base::sync_with_stdio(0); int t; cin >> t; while(t--) solve(); } ``` ::: ## D. "Country" Road (Easy Version) `tag`: `MST` 本題不是 `Shortest Path`, 仔細想想, 這題可以被轉為找到直徑最小的樹, `Shortest Path` 出來的樹不會符合。 `MST` 會符合的原因可以這樣想, 任取兩點相鄰的距離皆為最小, 那即可推到所有點的最大距離為最小。 :::spoiler `Solution` ```cpp= #include <bits/stdc++.h> using namespace std; template<class T, unsigned int N> using ar = array<T, N>; using ll = long long; #define F first #define S second using node = pair<ll, int>; const int mxN = 1e5; int n, m; vector<node> adj[mxN]; vector<int> graph[mxN]; void make_graph() { priority_queue<ar<ll,3>, vector<ar<ll,3>>, greater<ar<ll,3>>> pq; //[w, u, v] vector<bool> htop(n, 0); pq.push({0, 0, 0}); while(!pq.empty()) { auto t = pq.top(); pq.pop(); if(htop[t[2]]) continue; htop[t[2]] = 1; graph[t[1]].push_back(t[2]); for(auto& nd : adj[t[2]]) { if(!htop[nd.S]) pq.push({nd.F, t[2], nd.S}); } } for(int i = 0; i < n; ++i) { cout << i+1 << ' '; for(auto& nd : graph[i]) { if(nd != i) cout << nd+1 << ' '; } cout << '\n'; } } int main() { cin.tie(0), ios_base::sync_with_stdio(0); cin >> n >> m; ll u, v, w; for(int i = 0; i < m; ++i) { cin >> u >> v >> w, --u, --v; adj[u].push_back({w, v}); adj[v].push_back({w, u}); } make_graph(); } ``` ::: ## E. "Country" Road (Hard Version) `tag`: `LCA Euler Tour Technique` 本題是用 `pD` 畫出來的圖做詢問, 仔細的想一下是對子樹做詢問, 那就是`樹壓平 LCA Euler Tour Technique` 啦! 樹壓平的資料結構維護的是一個區間除法修改、區間加法查詢的資料, 但因為有小數點, 所以不用擔心不精準所產生的誤差。 至於要判是不是上級機關,就用 `LCA` 的 `isanc(u, v)`, 因為下級必是上級的子樹節點之一。 ```cpp= bool isanc(int u, int v) { return dfi[u] <= dfi[v] && dfo[v] <= dfo[u]; } ``` 至於實作的方法有很多種, 這裡提供兩個。 :::spoiler `Solution revcoding` ```cpp= #include <bits/stdc++.h> using namespace std; template<class T, unsigned int N> using ar = array<T, N>; using ll = long long; using ld = long double; #define F first #define S second using node = pair<ll, int>; #define mid ((l+r)/2) #define lc (id<<1) #define rc (id<<1|1) const int mxN = 1e5; int n, m, h[mxN], dfi[mxN<<1], dfo[mxN<<1], tag[mxN<<3], cnt = 1; vector<node> adj[mxN]; vector<int> graph[mxN]; unordered_map<int, int> pos; ld seg[mxN<<3]; void make_graph() { priority_queue<ar<ll,3>, vector<ar<ll,3>>, greater<ar<ll,3>>> pq; //[w, u, v] vector<bool> htop(n, 0); pq.push({0, 0, 0}); while(!pq.empty()) { auto t = pq.top(); pq.pop(); if(htop[t[2]]) continue; htop[t[2]] = 1; graph[t[1]].push_back(t[2]); for(auto& nd : adj[t[2]]) { if(!htop[nd.S]) pq.push({nd.F, t[2], nd.S}); } } } void dfs(int node) { int real = node-n; dfi[node] = cnt; dfi[real] = cnt, dfo[real] = cnt++; for(auto& nd : graph[real]) { if(nd != real) { dfs(n+nd); } } dfo[node] = cnt++; } void pull(int id, int l, int r) { if(l != r) seg[id] = seg[lc] + seg[rc]; } void push(int id, int l, int r) { if(tag[id]) { if(l != r) { tag[lc] += tag[id]; tag[rc] += tag[id]; seg[lc] /= (1ll<<min(60, tag[id])); seg[rc] /= (1ll<<min(60, tag[id])); tag[id] = 0; } } } void build(int id, int l, int r) { if(l == r) { if(pos.find(l) != pos.end()) seg[id] = h[pos[l]]; return; } build(lc, l, mid), build(rc, mid+1, r); pull(id, l, r); } void upd(int id, int l, int r, int ql, int qr, bool v) { push(id, l, r); if(ql <= l && r <= qr) { if(v) { seg[id] /= 2; ++tag[id]; } return; } if(l > qr || ql > r) return; upd(lc, l, mid, ql, qr, v), upd(rc, mid+1, r, ql, qr, v); pull(id, l, r); } ld qry(int id, int l, int r, int ql, int qr) { push(id, l, r); if(ql <= l && r <= qr) return seg[id]; if(l > qr || ql > r) return 0; return qry(lc, l, mid, ql, qr) + qry(rc, mid+1, r, ql, qr); } bool isanc(int u, int v) { return dfi[u] <= dfi[v] && dfo[v] <= dfo[u]; } void statics() { for(int i = 0; i < n; ++i) cin >> h[i]; dfs(n); --cnt; for(int i = 0; i < n; ++i) pos[dfo[i]] = i; build(1, 1, cnt); int q; cin >> q; while(q--) { int u, v; cin >> u >> v, --u, --v; u += n, v += n; if(isanc(u, v)) { upd(1, 1, cnt, dfi[v], dfo[v], 1); } else if(isanc(v, u)) { upd(1, 1, cnt, dfi[u], dfo[u], 1); } else { upd(1, 1, cnt, dfi[v], dfo[v], 1); upd(1, 1, cnt, dfi[u], dfo[u], 1); } cout << qry(1, 1, cnt, dfi[u], dfo[u]) << ' '; cout << qry(1, 1, cnt, dfi[v], dfo[v]) << '\n'; } } int main() { cin.tie(0), ios_base::sync_with_stdio(0); cin >> n >> m; ll u, v, w; for(int i = 0; i < m; ++i) { cin >> u >> v >> w, --u, --v; adj[u].push_back({w, v}); adj[v].push_back({w, u}); } cout << fixed << setprecision(9); make_graph(); statics(); } ``` ::: :::spoiler `Solution jakao` ```cpp= #include<bits/stdc++.h> #define MXN 100005 #define ll long long #define endl '\n' #define EPS #define cl(x) (x<<1) #define cr(x) (x<<1|1) #define INF 0x3f3f3f3f3f3f3f3f using namespace std; using ld = long double; int n,m,ti; int num[MXN],tin[MXN],tout[MXN]; ll pow2[62]; pair<int,int> rng[MXN]; vector<int> edge[MXN]; vector<pair<int,ll>> ori[MXN]; bool v[MXN]; priority_queue<pair<ll,pair<int,int>>,vector<pair<ll,pair<int,int>>>,greater<pair<ll,pair<int,int>>>> pq; struct seg_tree{ ld a[MXN<<1],val[MXN<<3]; int tag[MXN<<3],NO_TAG=0; void push(int i,int l,int r){ if(tag[i] != NO_TAG){ val[i]/=pow2[min(tag[i],60)]; if(l!=r){ tag[cl(i)]+=tag[i]; tag[cr(i)]+=tag[i]; } tag[i]=NO_TAG; } } void pull(int i,int l,int r){ int mid=(l+r)>>1; push(cl(i),l,mid);push(cr(i),mid+1,r); val[i]=val[cl(i)]+val[cr(i)]; } void build(int i,int l,int r){ if(l==r){ val[i]=a[l]; return; } int mid=(l+r)>>1; build(cl(i),l,mid);build(cr(i),mid+1,r); pull(i,l,r); } void update(int i,int l,int r,int ql,int qr){ push(i,l,r); if(ql<=l&&r<=qr){ tag[i]++; return; } int mid=(l+r)>>1; if(ql<=mid) update(cl(i),l,mid,ql,qr); if(qr>mid) update(cr(i),mid+1,r,ql,qr); pull(i,l,r); } ld query(int i,int l,int r,int ql,int qr){ push(i,l,r); if(ql<=l&&r<=qr) return val[i]; int mid=(l+r)>>1; double ret=0; if(ql<=mid) ret+=query(cl(i),l,mid,ql,qr); if(qr>mid) ret+=query(cr(i),mid+1,r,ql,qr); return ret; } }tree; void prim(){ pq.push(make_pair(0,make_pair(0,0))); while(!pq.empty()){ auto u=pq.top();pq.pop(); if(v[u.second.second]) continue; v[u.second.second]=1; if(u.second.first != u.second.second){ edge[u.second.first].push_back(u.second.second); } for(auto i:ori[u.second.second]){ if(!v[i.first]){ pq.push(make_pair(i.second,make_pair(u.second.second,i.first))); } } } } int dfs(int x){ tree.a[ti] = num[x]; tin[x] = rng[x].first = rng[x].second = ti++; for(auto i:edge[x]){ rng[x].second = dfs(i); } tout[x] = ti++; return rng[x].second; } bool isanc(int x,int y){ return tin[x]<=tin[y] && tout[x]>=tout[y]; } int main(){ ios::sync_with_stdio(0);cin.tie(0); cout<<fixed<<setprecision(9); pow2[0]=1; for(int i=1;i<=60;i++){ pow2[i]=pow2[i-1]*2LL; } cin>>n>>m; for(int i=0;i<m;i++){ ll u,v,w; cin>>u>>v>>w; --u,--v; ori[u].push_back(make_pair(v,w)); ori[v].push_back(make_pair(u,w)); } prim(); for(int i=0;i<n;i++) cin>>num[i]; dfs(0); tree.build(1,0,ti-1); int q;cin>>q; for(int a,b;q--;){ cin>>a>>b; --a,--b; if(isanc(a,b)){ tree.update(1,0,ti-1,rng[b].first,rng[b].second); cout<<tree.query(1,0,ti-1,rng[a].first,rng[a].second)<<" "<<tree.query(1,0,ti-1,rng[b].first,rng[b].second)<<endl; } else if(isanc(b,a)){ tree.update(1,0,ti-1,rng[a].first,rng[a].second); cout<<tree.query(1,0,ti-1,rng[a].first,rng[a].second)<<" "<<tree.query(1,0,ti-1,rng[b].first,rng[b].second)<<endl; } else{ tree.update(1,0,ti-1,rng[a].first,rng[a].second); tree.update(1,0,ti-1,rng[b].first,rng[b].second); cout<<tree.query(1,0,ti-1,rng[a].first,rng[a].second)<<" "<<tree.query(1,0,ti-1,rng[b].first,rng[b].second)<<endl; } } return 0; } ``` :::