發表文章

目前顯示的是有「樹論」標籤的文章

[ZeroJudge] e201: 期末打掃

題目連結: https://zerojudge.tw/ShowProblem?problemid=e201 稀有的過來寫個解。大學生活好累,每天都在被作業QAQ 一開始看錯題目了,不過因為沒看錯太多所以改個幾行就過了XD 看到這題我的第一個想法是樹DP,所以想說先想想看有沒有甚麼有用的性質。感受了一下發現我可以定義$f_u(x)$代表在$x$時間點進入$u$後再出來的時間點,那可以發現$f_u(x)=\max(a_{u0},x+d_u)$,其中$d_u$代表可以不用等待就都檢查完$u$的子樹的時間,$a_{u0}$代表如果是第0個時間點就從$u$出發那要多久後才能回到$u$並把所有教室都檢查完的時間。證明這部分成立的方法也很簡單(不過我很笨想了很久QAQ),假設在時間點$t\geq 0$,存在一種走法最佳,那我們可以知道他的時間一定$\geq t+d_u$,因為不管怎樣我們至少一定要把底下整棵子樹都走過一遍才能回來,另外我們也知道那個最佳走法的時間也$\geq a_{u0}$,因為如果他更小的話,我們在時間點$0$的時候就可以故意等到時間點$t$在走,這樣可以走出更加的解,與假設不合,這樣我們就證明了在任意時間點$t$的任意解$g$都有$g \geq f_u(t)$,而且明顯$f_u(t)$是可以達到的,因為我們可以照著$t=0$的路徑走,那如果中間很順利不用逗留的話就達到了$f_u(x)=x+d_u$,而若是要逗留的話,那在逗留的當下我們接著的時間就會跟$t=0$的時候同步了,所以這時$f_u(x)=a_{u0}$。 講了這麼多我們現在終於知道一個人在時間點$x$進入$u$後出來的時間,那接下來就是要決定當我們在$u$的時候該以怎樣的順序走訪他的子樹們$v$了,而這時我盯著$f_u(x)=\max(a_{u0},x+d_u)$的圖(一堆平移過後的 ReLU 函數w)一段時間後猜想順序就是按著$f_u$的轉折點的$x$座標由小到大拜訪就會是好的,也就是按照$a_{u0}-d_u$由小到大走訪就會是最佳的,不過這部分我證不太出來,所以後來就依靠 Z3 證明了對於任意$s,t,x_0\geq 0$都有$a_{s0} - d_s \leq a_{t0} - d_t \Rightarrow f_t(f_s(x_0)) \leq f_s(f_t(x_0))$,那我...

[Codeforces] 761E. Dasha and Puzzle

題目連結: http://codeforces.com/problemset/problem/761/E 一開始以為邊長只能是一www 會發現節點才30個,那我們可以簡單的安排每個深度的邊長$2^{55-d_i}$這樣的話,因為$\sum_{i=0}^{n-1}2^i #include <bits/stdc++.h> using namespace std; typedef long long lld; #define PB push_back typedef pair<lld,lld> PLL; #define FF first #define SS second const int N = 30 + 5; lld dx[]={0, 0, 1, -1}, dy[]={1, -1, 0, 0}; int inv[]={1, 0, 3, 2, 5}; vector<int> G[N]; PLL ans[N]; int dep[N]; void dfs(int,int,int,PLL); int main(){ ios_base::sync_with_stdio(0);cin.tie(0); int n; cin>>n; for(int i=0;i<n-1;i++){ int u, v; cin>>u>>v; G[u].PB(v); G[v].PB(u); } bool flag = false; for(int i=1;i<=n;i++) if(G[i].size() > 4){ flag = true; } if(flag){ cout<<"NO\n"; return 0; } cout<<...

[Codeforces] 698B. Fix a Tree

題目連結: http://codeforces.com/problemset/problem/698/B 一開始完全沒想法,被提示(?說就算非法也還是一張圖後才比較有想法。這題我的作法很greedy,就先判斷一下有沒有原本就有可以當根的點,有的話就把他當根,接下來再掃一遍,看看有沒有人會構成環,有的話就直接把他接到根上面,此外,若沒有現成的根的話就直接把他變成根。而要找誰跟誰一樣就用個並查集就好了XD #include <bits/stdc++.h> using namespace std; #define PB push_back #define FF first #define SS second const int N = 200000 + 5; class DJS{ private: vector<int> arr; public: void init(int n){ arr.resize(n); for(int i=0;i<n;i++) arr[i]=i; } int query(int x){ if(arr[x]!=x) arr[x] = query(arr[x]); return arr[x]; } void merge(int a, int b){ arr[query(a)]=arr[query(b)]; } } djs; int arr[N]; int main(){ ios_base::sync_with_stdio(0);cin.tie(0); int n, ans = 0, root = -1; cin>>n; for(int i=1;i<=n;i++){ cin...

[Codeforces] 570D. Tree Requests

題目連結: http://codeforces.com/contest/570/problem/D 把詢問依照節點存好,接下來我們就可以對樹DFS,同時記錄一下他的深度與每個字母出現的次數,然後要把兩棵子樹的資訊合併起來時就用啟發式合併,而完成一棵子樹後就可以把每個詢問的答案填上去,而且我們只在意奇偶性,所以甚至還可以用位元運算壓掉一些常數,大概就這樣吧。 #include <bits/stdc++.h> using namespace std; #define PB push_back typedef pair<int,int> PII; #define FF first #define SS second const int N = 500000 + 5; vector<int> G[N]; char wh[N]; vector<PII> que[N]; int ans[N], dep[N]; void dfs(int, map<int,int>&); int main(){ int n, q; scanf("%d%d",&n,&q); dep[1]=1; for(int i=2;i<=n;i++){ int x; scanf("%d", &x); G[x].PB(i); dep[i]=dep[x]+1; } scanf("%s", wh); for(int i=0;i<q;i++){ int v, d; scanf("%d%d", &v, &d); if(d < dep[v]) continue; que[v].PB({d, i}); } map<int,int> mp...

[TIOJ] 1214. 樹論 之 樹同構測試 (確定性作法)

題目連結: http://tioj.infor.org/problems/1214 前面 稍微講了一下用hash的作法,不過當參數取的不好時,hash是很有可能會撞的。如果一定要確定性的算法的話,可以把hash作法改成:對於葉節點先定義一個值,而對於非葉節點則蒐集起他所有子樹的hash值排序好後塞到map之類的容器並訂一個值(例如直接用map的size),那這樣複雜度會退化成$O(nlogn)$但是就不會有碰撞的問題了。 1214. 樹論 之 樹同構測試 4 1272 Accepted 100 Testdata no. Time (ms) Memory (KiB) Verdict 0 4 1272 Accepted Submitter: ototot Compiler: c++11 Code Length: 1.77 KB #include <bits/stdc++.h> using namespace std; #define FF first #define SS second #define PB push_back #define ALL(x) (x).begin(), (x).end() const int N = 100 + 5; vector<int> G1[N], G2[N]; int sz1[N], sz2[N]; map<vector<int>,int> bkt; inline void init(int); int GetCentroid(vector<int>[],int[],int,int,int); int Hash(vector<int>[],int,int); int main(){ ios_base::sync_with_stdio(0);cin.tie(0); int n; while(cin>...

[TIOJ] 1214. 樹論 之 樹同構測試 (Hash)

題目連結: http://tioj.infor.org/problems/1214 原本以為可以拿個中序前序之類的來判,但感覺還是慘慘的。後來才知道原來其實可以把一整棵樹的形狀hash起來,這樣的話就可以直接看hash值來判了,不過要怎麼hash呢?這裡的作法是先定義葉節點的hash值,而其他非葉節點則將他所有子樹的hash值平方後加起來,這樣只要根一樣的話那hash出來的值就會一樣(但有可能有碰撞),但是這題給的是棵無根樹,若沒有用一些好方法定根,那hash出來的值就很有可能不一樣,這裡利用樹的一個特殊的性質-一顆樹的重心最多只有兩顆,所以用重心當根判一下就可以有$O(n)$的複雜度了。 #include <bits/stdc++.h> using namespace std; typedef unsigned long long llu; #define FF first #define SS second #define PB push_back #define ALL(x) (x).begin(), (x).end() const int N = 100 + 5; const int mod = 44478311; vector<int> G1[N], G2[N]; int sz1[N], sz2[N]; inline void init(int); int GetCentroid(vector<int>[],int[],int,int,int); llu Hash(vector<int>[],int,int); int main(){ ios_base::sync_with_stdio(0);cin.tie(0); int n; while(cin>>n, n){ init(n); for(int i=0;i<n-1;i++){ int u, v; cin>>u>>v; ...

[TIOJ] 1106. 遇見一株樹

題目連結: http://tioj.infor.org/problems/1106 葉子個數就只要算*的個數就好了,深度則只要看有幾個括號(若是遇到右括號就要把他移掉),而幾元樹則是在每一層都把()合成一個之後再加上*的個數。 #include <bits/stdc++.h> using namespace std; int main(){ ios_base::sync_with_stdio(0);cin.tie(0); string ss; while(cin>>ss){ int l=0, d=0, y=0; int cnt=0; stack<int> sk; sk.push(0); for(auto c:ss){ if(c=='('){ sk.top()++; sk.push(0); cnt++; }else if(c==')'){ y=max(y, sk.top()); sk.pop(); cnt--; } d=max(d, cnt); if(c=='*'){ l++; sk.top()++; } } cout<<l<<" "<<d+1<<" "<<y<<'\n'; } return 0; }

[NTUJ] 0903. Shadow Hunters

題目連結: http://acm.csie.org/ntujudge/problem.php?id=903 有趣的好題,想了很久只想的到樹鍊剖分的作法,被雷了才知道更好的做法QQ 對於全子樹加一其實很簡單,就把樹壓扁後把$[in[x], out[x]]$全部加一就好,然後詢問時則單點詢問邊即可。但是另外兩個操作就會變得很難做,所以要用另外一種方式想,考慮一條邊若或被加值,則一定是因為它下面的點有被加值,所以其實對最底下的點加值即可,然後為了怕太上面的人也會被算到,記得超過最頂之後要再減一以免多算,這樣就變成單點加值區間查詢了。 #include <bits/stdc++.h> using namespace std; #define PB push_back const int N = 100000 + 5; class BIT1{ #define lowbit(x) ((x)&(-(x))) private: int arr[N], size; inline int query(int x){ int r=0; while(x){ r+=arr[x]; x-=lowbit(x); } return r; } public: inline void init(int x){ fill(arr, arr+size, 0); size=x; } inline int query(int l, int r){ // 1-base (l, r] return query(r)-query(l); } inline void add(int x,...

[TIOJ] 1609. Problem C 二元搜尋樹 (TRVBST)

題目連結: http://tioj.infor.org/problems/1609 其實一棵二元搜尋樹中序遍歷就是大小關係,所以直接把他給你的數字由小到大sort過一遍後印出來就是答案了 #include <bits/stdc++.h> using namespace std; int arr[1000005]; int main(){ ios_base::sync_with_stdio(0);cin.tie(0); int n;cin>>n; for(int i=0;i<n;i++)cin>>arr[i]; sort(arr,arr+n); for(int i=0;i<n;i++)cout<<arr[i]<<' '; return 0; }

[TIOJ] 1108. 樹的三兄弟

題目連結: http://tioj.infor.org/problems/1108 因為二元搜尋樹有幾個棒棒的性質,那就是依照前序遍歷插入會得到原樹(後序倒著插入也會是好的),而中序遍歷即為其大小關係,故只要用中序得到如何比大小,用前序插入就可建構出原樹,再自己後序一遍就好XDD,而雖然這題是二元樹,但你還是可以把它當二元搜尋樹搞 #include <bits/stdc++.h> using namespace std; int Map[256]; struct node{ node* l; node* r; char x; node(){l=NULL;r=NULL;} node(char a){l=NULL;r=NULL;x=a;} }; void insert(node*,node*&); void remove(node*); void dfs(node*); int main(){ char pre[60], in[60]; while(scanf("%s\n%s",pre,in)!=EOF){ vector<node*> vv; int sz=strlen(in); for(int i=0;i<sz;i++)Map[in[i]]=i; auto root = new node(pre[0]); for(int i=1;i<sz;i++){ auto tmp = new node(pre[i]); insert(tmp,root); vv.push_back(tmp); } dfs(root);putchar('\n'); remove(root); } return 0;...

[TIOJ] 1292. H.佔邊砍樹

題目連結: http://tioj.infor.org/problems/1292 裸樹上最小點覆蓋。而樹上最小點覆蓋該如何做呢?,不妨考慮先DFS一遍取得整棵樹的構造,之後從葉子走上去(可直接用DFS序列倒著跑),若自己及自己的爸爸都沒有被任何點覆蓋,則把自己的爸爸放到點覆蓋集,因為這樣可以保證把所有點都放進去,而且因為是放爸爸進去,所以可以拿到最小的點覆蓋集。 #include <bits/stdc++.h> using namespace std; #define N 10000 vector<int> tree[N+10]; int deep[N+10]; vector<int> arr; bitset<N+10> poped; void DFS(int,int,int); int n; int main(){ scanf("%d",&n); for(int i=0;i<n-1;i++){ int s,e; scanf("%d%d",&s,&e); tree[s].push_back(e); tree[e].push_back(s); } int cnt=0; DFS(1,-1,0); for(int i=arr.size()-1;i>0;i--){ int cc=arr[i]; int ff=arr[i-1]; if(deep[ff]>=deep[cc])continue; if(!poped[ff]&&!poped[cc]){ poped[ff]=1; poped[cc]=1; cnt++; } ...

[TIOJ] 1947. 小向的試煉 1-3:森林(Forest)

題目連結: http://tioj.infor.org/problems/1947 裸樹重心題,而要怎麼找樹重心呢?直接DFS吧,在DFS時你就知道他小孩的子樹大小,而他祖先那塊子樹不難發現就是點數減下子樹的數量,然後就記個最小值大概這樣吧 #include <cstdio> #include <vector> #include <algorithm> typedef long long lld; using std::max; using std::min; using std::vector; lld n,m=2147483647999; vector<int> graph[1000010]; inline lld DFS(int,int); int main(){ scanf("%d",&n); for(int i=0;i<n-1;i++){ int a,b; scanf("%d%d",&a,&b); graph[a].push_back(b); graph[b].push_back(a); } DFS(0,-1); printf("%lld",m); return 0; } inline lld DFS(int k,int f){ lld size=1; lld sub=0; for(int i=0;i<graph[k].size();i++){ if(graph[k][i] == f)continue; lld each=DFS(graph[k][i],k); sub=max(each, sub); size+=each; } ...