Codeforces Round #787 (Div. 3) 题解
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\),那么:
最后求 \(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 分钟比同学慢巨大多,复健大失败。

浙公网安备 33010602011771号