Codeforces Round #787 (Div. 3) 題解
- A - Food for Animals
- B - Make It Increasing
- C - Detective Task
- D - Vertical Paths
- E - Replace With the Previous, Minimize
- F - Vlad and Unfinished Business
- G - Sorting Pancakes
- 瞎扯
A - Food for Animals
判一下 \(a+c>=x,b+c>=y,a+b+c>=x+y\) 即可。
aaaaaaaaaaacccccccccccbbbbbbbbbb xxxxxxxxxxxxx.....yyyyyyyyyyyyyy
大概就是這樣的分配方式,感性理解一下正確性。
#include<bits/stdc++.h> using namespace std; int main(){ ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int T; cin>>T; while(T--){ int a,b,c,x,y; cin>>a>>b>>c>>x>>y; cout<<(a+c>=x&&b+c>=y&&a+b+c>=x+y?"YES\n":"NO\n"); } return 0; }
B - Make It Increasing
貪心地從後往前遍歷一遍陣列,在當前位置的值不小於後一個位置的值的時候不斷除以二。
當非序列第一個元素的值為 \(0\) 的時候沒有合法答案。
#include<bits/stdc++.h> using namespace std; void mian(){ int n; cin>>n; vector<int> a(n); for(int &x:a)cin>>x; int ans=0; for(int i=a.size()-2;i>=0;i--){ if(a[i+1]==0){ cout<<"-1\n"; return; } while(a[i]>=a[i+1]){ a[i]/=2; ans++; } } cout<<ans<<'\n'; } int main(){ ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int T; cin>>T; while(T--)mian(); return 0; }
C - Detective Task
如果能確定某一個是小偷,其他的就都不用懷疑了(為什麼不顯式說明這個啊啊啊),所以首先排掉開頭說 \(0\) 或者末尾說 \(1\) 的。
之後看到如果一個人說了 \(0\) 但後面有人說 \(1\) 他就是嫌疑人,反之亦然。這一步可能會有 \(1\) 或 \(2\) 個嫌疑人(\(2\) 個有且僅有一種情況,就是一對 \(0\) 和 \(1\) 反了,看程式碼),有就能直接輸出不用做下一步了。
現在所有人說的話都是合法的,所以可以懷疑最後一個 \(1\) 到第一個 \(0\) 之間的所有人。
#include<bits/stdc++.h>
using namespace std;
int n,p0[200005],s1[200005];
char s[200005];
void mian(){
cin>>s+1;
n=strlen(s+1);
if(s[1]=='0'||s[n]=='1'){
cout<<"1\n";
return;
}
for(int i=1;i<=n;i++)p0[i]=p0[i-1]||s[i]=='0';
s1[n+1]=0;
for(int i=n;i>=1;i--)s1[i]=s1[i+1]||s[i]=='1';
int th=0;
for(int i=1;i<=n;i++)if(s[i]=='0'&&s1[i])th++;
for(int i=1;i<=n;i++)if(s[i]=='1'&&p0[i])th++;
if(th){
cout<<th<<'\n';
return;
}
int l=1;
while(s1[l+1])l++;
int r=n;
while(p0[r-1])r--;
cout<<r-l+1<<'\n';
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T;
cin>>T;
while(T--)mian();
return 0;
}
D - Vertical Paths
你用一些路徑合併出這個樹,本質就是一個有 \(C\) 個兒子(下掛結點)的結點,要斷開它連向兒子其中的的 \(C-1\) 條邊。然後怎麼斷都不會影響答案,所以 DFS 就行了。
#include<bits/stdc++.h>
using namespace std;
int n,rt;
vector<int> g[200005];
vector<stack<int>> ans;
int dfs(int x){
if(g[x].empty()){
ans.emplace_back();
ans.back().emplace(x);
return ans.size()-1;
}else{
bool f=1;
int res=0;
for(int y:g[x])if(f){
ans[res=dfs(y)].emplace(x);
f=0;
}else{
dfs(y);
}
return res;
}
}
void mian(){
for(int i=1;i<=n;i++)g[i].clear();
ans.clear();
cin>>n;
for(int i=1;i<=n;i++){
int fa;
cin>>fa;
if(fa==i)rt=i;
else g[fa].emplace_back(i);
}
dfs(rt);
cout<<ans.size()<<'\n';
for(auto &s:ans){
cout<<s.size()<<'\n';
while(!s.empty()){
cout<<s.top()<<' ';
s.pop();
}
cout<<'\n';
}
cout<<'\n';
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T;
cin>>T;
while(T--)mian();
return 0;
}
E - Replace With the Previous, Minimize
考慮字典序最小,就是先最小化第一個字元,然後第二個,再第三個,以此類推。
然後就從頭到尾這麼跑一遍,每次不斷嘗試縮小當前位置的字元,直到當前字元變成 a(顯然沒有縮小 a 的必要對吧)。
注意到你有一個 b 要變成 a,和一個 c 要變成 a,你可以先 c 變成 b,再 b 變成 a(和 b 與 c 在字串中的位置無關),所以你可以記錄一下每一個字元有沒有變小,就不用重複做變小的操作了。
所以其實 k 最大 25,但是可能比較提示你所以題面就沒寫。
#include<bits/stdc++.h>
using namespace std;
int n,k;
string s;
bool p[26];
void mian(){
cin>>n>>k;
cin>>s;
memset(p,0,sizeof(p));
for(char c:s){
if(c!='a'){
int i=c-'a';
while(k&&i&&!p[i]){
p[i--]=1;
k--;
}
}
}
for(char &c:s){
while(p[c-'a'])c--;
}
cout<<s<<'\n';
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T;
cin>>T;
while(T--)mian();
return 0;
}
F - Vlad and Unfinished Business
把起點和終點之間的路徑搞出來,然後一路上有要訪問的點的分支就進去 DFS 一遍。
我們把起點和終點和要訪問的點都塗黑,發現答案就是這樣的:
把包含所有黑點的最小的一個子樹的邊數叫做 \(C\),起點和終點之間距離叫做 \(D\),答案就是 \(2 \times C - D\),表示起點到終點的路徑上每個有黑點的分支都要進去 DFS 一次,然後起點到終點的路徑多算了一次所以減去。
這個 \(C\) 你可以維護所有黑點的集合,每次把深度最大的黑點往上移一次,記錄經過的邊,最後所有黑點會聚到一起,此時所有經過的邊總數就是 \(C\)。
大概複雜度是 \(O(n \log_2 n)\) 的。(算 \(C\) 的時候達到這個複雜度,\(D\) 應該 BFS 或 DFS 都行)
程式碼我就只貼連結了,因為計算 \(C\) 的方式寫複雜了。https://codeforces.com/contest/1675/submission/155959860
G - Sorting Pancakes
一眼 DP。
考慮固定一個字首的煎餅數量,每次列舉下一個位置的煎餅數量,同時為了保證列舉完全部的煎餅後,總數對的,我們再記錄一個字首煎餅數量總和。
那麼狀態就是 \(f_{i,h,s}\),\(i\) 表示字首長度,\(h\) 表示最後一個煎餅堆的高度,\(s\) 表示字首煎餅數量和。
然後就是計算代價了,我們考慮在第 \(i\) 和 \(i+1\) 堆煎餅之間移動了 \(c_i\) 次煎餅,那麼答案就是 \(\sum c_i\)。
於是你就記 \(b_i = \sum_{j=1}^{j<=i} a_j\),那麼:
\[f_{i,h,s} = \min f_{i-1,ph,s-h} + |s - b_i| \]最後求 \(answer = \min f_{n,h,m}\)
看上去是 \(O(n m^3)\) 不可過的,實際上合法的狀態中 \(i \times h \le s\),也就是說所有位置最大的 \(h\) 的和大約是 \(m \log_2 m\) 級別的(證明搜調和級數),然後就成為了 \(O(n m^2 \log m)\)。不是很懂這裡的對數的底數是啥,反正不小於 \(2\) 就是了。
#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
int n,m,a[255],b[255],f[255][255][255];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
b[i]=b[i-1]+a[i];
}
memset(f,0x3f,sizeof(f));
f[0][m][0]=0;
int ans=inf;
for(int i=1;i<=n;i++){
for(int ph=m;ph>=0;ph--){
for(int ps=m;ps>=ph*(i-1);ps--)if(f[i-1][ph][ps]!=inf){
for(int h=min(m-ps,ph);h>=0;h--){
f[i][h][ps+h]=min(f[i][h][ps+h],f[i-1][ph][ps]+abs(ps+h-b[i]));
if(i==n&&ps+h==m)ans=min(ans,f[i][h][ps+h]);
}
}
}
}
cout<<ans<<'\n';
return 0;
}
瞎扯
感覺 E < D < C。
最後一題做了 19 分鐘比同學慢巨大多,復健大失敗。