--- tags: DDJ_Contest --- # DDJ Regular Contest Round\#3 Tutorial ## [A. 質數總和](http://203.64.191.163/ShowProblem?problemid=a620) tags: ```brute force, number theory``` 窮舉所有區間,用 $O(\sqrt{N})$ 或以下的複雜度 判斷當下區間是不是質數,是就加上總和 複雜度 $O(T \sqrt{N})$ 判斷質數教學: http://web.ntnu.edu.tw/~algo/Prime2.html#2 :::spoiler Solution ```cpp #include<bits/stdc++.h> #define endl '\n' using namespace std; bool isprime(int x){ if(x<=1) return 0; for(int i=2;i*i<=x;i++) if(x%i==0) return 0; return 1; } int main(){ ios::sync_with_stdio(0);cin.tie(0); int t;cin>>t; string n; while(t--){ int ans=0; cin>>n; for(int i=0;i<n.size();i++){ string tmp; for(int j=i;j<n.size();j++){ tmp+=n[j]; int x=atoi(tmp.c_str()); if(isprime(x)){ ans+=x; } } } cout<<ans<<endl; } return 0; } ``` ::: ## [B. 模除排列](http://203.64.191.163/ShowProblem?problemid=a615) tags: ```brute force``` 爆搜剪枝 直接遞迴窮舉所有組合, 記得每次要先判斷當下要放的數字排在是否能排在前一個數字後面 不能最後窮舉完排列才檢查 由於多筆測資,可能會出現重複的詢問,如果每次都重新計算會TLE 因此算過的詢問要建表記起來,重複就直接查表 然後原題網址XD: https://codeforces.com/gym/103102/problem/I :::spoiler Solution ```cpp= #include<bits/stdc++.h> #define ll long long #define MXN 20 #define endl '\n' using namespace std; int len; bool v[MXN], used[MXN]; vector<int> arr; vector<vector<int>> ans[MXN]; void dfs(int x){ if(x==len){ if(arr[len-1]%arr[0]<=2) ans[len].push_back(arr); return; } for(int i=1;i<=len;i++){ if(!v[i] && (arr.empty()||arr.back()%i<=2)){ v[i]=1; arr.push_back(i); dfs(x+1); arr.pop_back(); v[i]=0; } } } int main(){ ios::sync_with_stdio(0);cin.tie(0); int t,n;cin>>t; while(t--){ cin>>n; if(used[n]){ for(auto i:ans[n]){ for(auto j:i) cout<<j<<" "; cout<<endl; } } else{ used[n]=1; len=n; dfs(0); for(auto i:ans[l]){ for(auto j:i) cout<<j<<" "; cout<<endl; } } } return 0; } ``` ::: ## [C. 水庫危機](http://203.64.191.163/ShowProblem?problemid=a612) tags: <code>dsu, ~~*LCT*~~</code> ~~可以直接砸LCT~~ 想想看,如果題目要求反過來把原本每個 $\#$ 變成 $W$ 有沒有比較好做 如果有想到作法的話,那就可以直接從最後面反過來做回去, 把每個操作紀錄起來 接著把原本的圖變成最後的情況,然後反向依序做回來 用一個並查集維護是不是同一個水區 複雜度 $O(NM \alpha(NM))$ 並查集教學: https://cp-algorithms.com/data_structures/disjoint_set_union.html :::spoiler Solution ```cpp= #include<bits/stdc++.h> #define MXN 2002 #define MXQ 100005 #define endl '\n' using namespace std; int n,m,q; int ans[MXQ],f[MXN*MXN]; pair<int,int> query[MXQ]; bool v[MXN*MXN]; string land[MXN]; int find(int x){ return f[x] == x ? x : f[x] = find(f[x]); } void merge(int x,int y){ x = find(x),y =find(y); if(x==y) return; f[y]=x; } int toord(int x,int y){ return x*n+y; } int main(){ ios::sync_with_stdio(0);cin.tie(0); cin>>n>>m; iota(f,f+n*m,0); for(int i=0;i<n;i++) cin>>land[i]; cin>>q; for(int i=0,a,b;i<q;i++){ cin>>a>>b; --a,--b; query[i]=make_pair(a,b); land[a][b]='#'; } for(int i=0;i<n;i++){ for(int j=0;j<m;j++){ v[toord(i,j)]=1; if(!i && !j) continue; if(land[i][j] == 'W'){ if(i && land[i-1][j]=='W') merge(toord(i,j),toord(i-1,j)); if(j && land[i][j-1]=='W') merge(toord(i,j),toord(i,j-1)); } } } int sum=0; for(int i=0;i<n;i++){ for(int j=0;j<m;j++){ if(land[i][j]=='W'){ sum+=v[find(toord(i,j))]; v[find(toord(i,j))]=0; } } } ans[q-1]=sum; for(int i=q-1;i>0;i--){ int a=query[i].first,b=query[i].second,cnt=0; land[a][b]='W'; if(a && land[a-1][b]=='W'){ cnt+=find(toord(a-1,b)) != find(toord(a,b)) merge(find(toord(a-1,b)), find(toord(a,b))); } if(b && land[a][b-1]=='W'){ cnt+=find(toord(a,b-1)) != find(toord(a,b)) merge(find(toord(a,b-1)), find(toord(a,b))); } if(a+1<n && land[a+1][b]=='W'){ cnt+=find(toord(a+1,b)) != find(toord(a,b)) merge(find(toord(a+1,b)), find(toord(a,b))); } if(b+1<m && land[a][b+1]=='W'){ cnt+=find(toord(a,b+1)) != find(toord(a,b)) merge(find(toord(a,b+1)), find(toord(a,b))); } sum-=cnt-1; ans[i-1]=sum; } for(int i=0;i<q;i++) cout<<ans[i]<<endl; return 0; } ``` ::: ## [D. 茴芠數回文數](http://203.64.191.163/ShowProblem?problemid=a610) tags: <code>strings, manacher, palindrome tree</code> 可以用 manacher 求出以每個字元為中心的最長回文長度 接著分奇數偶數兩種case做, 分別再記錄前綴和,可以達到 $O(N)$建表 $O(1)$查表 複雜度 $O(N+Q)$ 如果懶得建表,也可以二分搜有幾個長度 $\ge q_i$ 複雜度 $O(N+QlgN)$ manacher教學: https://cp-algorithms.com/string/manacher.html 當然也可以直接砸回文樹,把每種狀態看他長度有多長就存起來,再 $O(1)$ 查表,有興趣的再自已去查 :::spoiler Solution ```cpp= void solve() { int n;cin>>n; string str,v="^";cin>>str; for(const auto& i:str) v+="#"s+i; v+="#$"s; V<int> rad(v.size(),0); int c=0,r=0; for(int i=1;i<int(v.size())-1;i++){ int mirror_i = 2*c-i; if(i<r&&mirror_i>=1&&(i+rad[mirror_i])<r){ rad[i]=rad[mirror_i]; continue; } while(v[i-1-rad[i]]==v[i+1+rad[i]]) rad[i]++; if(i+rad[i]>r) r=i+rad[i],c=i; } V<int> arr(n+1,0); for(const auto& i:rad) arr[i]++; for(int i=n-2;i>0;i-=2) arr[i]+=arr[i+2]; for(int i=n-3;i>0;i-=2) arr[i]+=arr[i+2]; int q;cin>>q; while(q--){ int k;cin>>k; cout<<arr[k]<<' '; } cout<<endl; } ``` ::: ## [E. 填填看](http://203.64.191.163/ShowProblem?problemid=a606) tags: ```brute force, meet in the middle``` 總共有8個變數,如果直接窮舉 $O(N^8)$ 複雜度是爛掉 先把式子簡化: $\displaystyle \frac{a*b+c*d}{e} +f = g - h$ 變成 $a*b+c*d = e * (g-h-f)$ 接著可以用中間相遇法,先 $O(N^4)$ 窮舉前四個 $a,b,c,d$ 所有組合答案存進去一個容器裡(可以用hashtable或者二分搜,用map會TLE) 再花 $O(N^4)$ 窮舉後四個 $e,f,g,h$ 所有組合答案,每次 $O(lg{N^4})$ 時間去查有幾個值等於的數量 記得要處理 $e=0$ 要排除 複雜度 $O(N^4 lg N^4)$ :::spoiler Solution ```cpp= #include<bits/stdc++.h> #define ll long long #define MXN 105 #define endl '\n' using namespace std; ll n; ll arr[MXN]; unordered_map<ll,ll> mp; int main(){ ios::sync_with_stdio(0);cin.tie(0); cin>>n; for(int i=0;i<n;i++) cin>>arr[i]; for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ for(int k=0;k<n;k++){ for(int l=0;l<n;l++){ mp[arr[i]*arr[j]+arr[k]*arr[l]]++; } } } } ll ans=0; for(int i=0;i<n;i++){ if(!arr[i]) continue; //記得特判 for(int j=0;j<n;j++){ for(int k=0;k<n;k++){ for(int l=0;l<n;l++){ if(mp.count(arr[i]*(-arr[j]+arr[k]-arr[l]))){ ans+=mp[arr[i]*(-arr[j]+arr[k]-arr[l])]; } } } } } cout<<ans<<endl; return 0; } ``` :::