前言
昨晚因为感觉状态不太佳于是没有选择打Round 1024而是来补这场的题,结果发现这场Div3能够AK,并且最后一题DP挺有意思的,于是就来写这场的题解了
题解部分
A. Food for Animals
店里有a包狗粮b包猫粮c包两个都能吃,问你是否能让x只狗y只猫每只动物都有一包食物
签到题,判断c是否能弥补给猫以及给狗的粮食不足的部分就可以了
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define lowbit(x) x&(-x)
const int MOD=1e9+7;
const int N=1000100;
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
int Base = uniform_int_distribution<>(8e8,9e8)(rng);
int a[N];
void solve() {
init();
int num1,num2,num3;
int x,y;
cin>>num1>>num2>>num3>>x>>y;
if(max(0ll,x-num1) + max(0ll,y-num2) <= num3) cout<<"YES\n";
else cout<<"NO\n";
}
signed main() {
//ios::sync_with_stdio(false);
//cin.tie(0);
//cout.tie(0);
//cout<<prime[cnt-1]<<"\n";
//for(int i=1;i<=cnt;i++) cout<<prime[i]<<"\n";
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
B. Make It Increasing
给你一个n长度的数组a,你可以对任意一个数进行除2(向下取整),问你至少要多少次操作才能让数组非递增
很简单,直接贪心就可以了,如果从前往后,前面的数可能需要进行调整,所以直接从后往前贪心模拟就好了
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define lowbit(x) x&(-x)
const int MOD=1e9+7;
const int N=1000100;
int a[N];
void solve() {
init();
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
int res=0;
for(int i=n-1;i>=1;i--) {
while(a[i] && a[i] >= a[i+1]) a[i]/=2,res++;
if(a[i] >= a[i+1]) {
cout<<-1<<"\n";
return ;
}
}
cout<<res<<"\n";
}
signed main() {
//ios::sync_with_stdio(false);
//cin.tie(0);
//cout.tie(0);
//cout<<prime[cnt-1]<<"\n";
//for(int i=1;i<=cnt;i++) cout<<prime[i]<<"\n";
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
C. Detective Task
题意如下
由于除了盗贼之外的人要么忘记了要么只会讲真相,所以在盗贼之前的人不会说画不见了,在盗贼之后的人不会说画还在,所以只需要找到有多少个位置,前面没有0,后面没有1即可
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define lowbit(x) x&(-x)
const int MOD=1e9+7;
const int N=1000100;
int a[N];
void solve() {
init();
string s;
cin>>s;
int prezero = 0;
int len = s.length();
for(int i=0;i<len;i++) {
if(s[i] == '0') prezero ++;
}
int res=0;
for(int i=len-1;i>=0;i--) {
if(s[i]=='0') prezero--;
if(prezero == 0) res++;
if(s[i]=='1') break;
}
cout<<res<<"\n";
}
signed main() {
//ios::sync_with_stdio(false);
//cin.tie(0);
//cout.tie(0);
//cout<<prime[cnt-1]<<"\n";
//for(int i=1;i<=cnt;i++) cout<<prime[i]<<"\n";
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
D. Vertical Paths
给定一棵树,要求你输出最少的路径从而包含所有的节点,其中路径的要求是一定是从离根节点近的点到从根节点远的点
纯模拟题,我想要路径最少,那肯定每条路径都一直到叶子节点的地方,所以路径个数就是叶子节点个数,然后bfs + dfs输出,每次bfs找根节点,dfs顺着这个节点一直往下搜就好。
那可能有的人就要问了,我要输出路径个数,路径个数就是叶子节点个数,让然后我又要先输出路径长度,再挨个输出路径上的节点,我要怎么才能快速做到这几点呢。首先我要找完所有节点的方式找到根节点,在这个过程中我就能找到有多少个节点是没有子节点,也就是叶子节点的
其次,我从这个节点出发,如果我有多个子节点的时候,我的路径肯定是只能选择一个子节点,那么剩下的子节点就会成为其他路径的出发点,所以将这些节点放进队列中,然后再顺着选择的节点dfs这一条路径存起来
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define lowbit(x) x&(-x)
const int MOD=1e9+7;
const int N=1000100;
int a[N];
vector<int>G[N];
queue<int>out;
queue<int>q;
void dfs(int x) {
out.push(x);
for(int i=1;i<G[x].size();i++) {
q.push(G[x][i]);
}
if(G[x].size()) dfs(G[x][0]);
}
void solve() {
init();
int n;
cin>>n;
for(int i=1;i<=n;i++) G[i].clear();
map<int,int>mp;
int num=1;
for(int i=1;i<=n;i++) {
cin>>a[i];
if(a[i]==i) q.push(a[i]);
else {
mp[a[i]]++;
if(mp[a[i]] > 1) num++;
G[a[i]].push_back(i);
}
}
cout<<num<<"\n";
while(!q.empty()) {
int u = q.front();
q.pop();
dfs(u);
cout<<out.size()<<"\n";
while(!out.empty()) {
cout<<out.front()<<" ";
out.pop();
}
cout<<"\n";
}
cout<<"\n";
}
signed main() {
//ios::sync_with_stdio(false);
//cin.tie(0);
//cout.tie(0);
//cout<<prime[cnt-1]<<"\n";
//for(int i=1;i<=cnt;i++) cout<<prime[i]<<"\n";
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
E. Replace With the Previous, Minimize
给定一个字符串,每次操作都能将字符串里的某个字母全都变为字典序比它小一位的字母b变成a,a变成z
你有k次操作次数,问你能够获得字典序最小的字符串是什么
既然要的是字典序最小的字符串,我们肯定要从前往后贪心,尽可能将字母转换为a。这里需要注意的一个点,假设一个字符串cbk,现在我对第一个字母进行一次操作,那么字符串会变成bbk,再进行一次操作就会变成aak
那么我最多只会进行25次操作,所以我们直接暴力就好,从前往后尽可能将字符变成a字母,然后记得要暴力找一遍更改的范围,比如前面例子中,进行完两次操作之后,所有c到a字母都会变成a字母,记录更改范围内的字母最后变成了什么字母,这样子轮到后面进行操作的时候,我们就可以在已经变成某个字母的情况直接跳过
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define lowbit(x) x&(-x)
const int MOD=1e9+7;
const int N=1000100;
int a[N];
void solve() {
init();
int n,k;
cin>>n>>k;
string s;
cin>>s;
map<char,int>mp;
for(int i=0;i<26;i++) {
char ch = (char)('a' + i);
mp[ch] = i;
}
for(int i=0;i<n;i++) {
int num = mp[s[i]];
int ori = num;
while(num && k) {
num--;
k--;
char ch = (char)(num + 'a');
num = mp[ch];
}
for(int j=25;j>num;j--) {
char ch = (char)(j + 'a');
if(mp[(char)(j + 'a')] <= ori) mp[ch] = num;
}
}
for(int i=0;i<n;i++) cout<<(char)(mp[s[i]] + 'a');
cout<<"\n";
}
signed main() {
//ios::sync_with_stdio(false);
//cin.tie(0);
//cout.tie(0);
//cout<<prime[cnt-1]<<"\n";
//for(int i=1;i<=cnt;i++) cout<<prime[i]<<"\n";
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
F. Vlad and Unfinished Business
给你有n个节点的树,你要从x走到y,与此同时有一个k大小的数组a[i],问你最少需要经过多少条边才能走过所有 a i a_i ai节点的之后到达y,到达 a i a_i ai的顺序不规定
首先第一点:从x到y这条路径肯定要走,并且肯定只走一遍
然后再想明白一点,我肯定是从x到y的时候,顺路再去 a i a_i ai,那么我肯定是在x到y这条路径上,离 a i a_i ai最近的时候才会去 a i a_i ai,换句话来说,在x到y这条路径上,当某个节点的支路上有需要经过的节点,我就从这个节点出发,到需要经过的节点,然后再回来,那么增加的需要经过的边数就是这个支路上的边的个数的两倍
结论很简单,这道题难点在于如何用代码实现
我这里用了两次dfs,第一个dfs找的是从x到y的路径,第二次dfs找的是要找到a[i]的支路,并且用一个vis数组,为1表示这个节点在从x到y的路径上,为2表示这个节点在支路上,为3表示已经在第二次dfs找过这个节点
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define lowbit(x) x&(-x)
const int MOD=1e9+7;
const int N=1000100;
int a[N];
vector<int>G[N];
int vis[N];
int sign;
map<int,int>mp;
int sum;
void dfs1(int x,int tar) {
vis[x] = 1;
//cout<<x<<"\n";
if(x == tar) {
sign = 1;
return ;
}
for(int j=0;j<G[x].size();j++) {
int son = G[x][j];
if(!vis[son] && !sign) {
dfs1(son,tar);
}
}
if(!sign) vis[x] = 0;
//cout<<x<<" "<<vis[x]<<"\n";
}
void dfs2(int x) {
vis[x] = 3;
if(mp[x] == 1) {
vis[x] = 2;
}
for(int j=0;j<G[x].size();j++) {
int son = G[x][j];
if(!vis[son]) {
dfs2(son);
if(vis[son] == 2) vis[x]=2;
}
}
}
void solve() {
sign = 0;
mp.clear();
sum = 0;
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++) G[i].clear(),vis[i]=0;
int x,y;
cin>>x>>y;
//cout<<x<<"to"<<y<<"\n";
for(int i=1;i<=k;i++) {
int thing;
cin>>thing;
mp[thing] = 1;
}
for(int i=1;i<=n-1;i++) {
int u,v;
cin>>u>>v;
G[u].push_back(v);
G[v].push_back(u);
}
dfs1(x,y);
for(int i=1;i<=n;i++) {
if(vis[i] == 1) {
sum++;
dfs2(i);
vis[i]=1;
}
}
sum--;
for(int i=1;i<=n;i++) {
//cout<<vis[i]<<" ";
if(vis[i]==2) sum+=2;
}
//cout<<"\n";
cout<<sum<<"\n";
}
signed main() {
//ios::sync_with_stdio(false);
//cin.tie(0);
//cout.tie(0);
//cout<<prime[cnt-1]<<"\n";
//for(int i=1;i<=cnt;i++) cout<<prime[i]<<"\n";
int t=1;
cin>>t;
while(t--) {
solve();
}
return 0;
}
G. Sorting Pancakes
总共有m块饼,放在n个盘子上, a i a_i ai表示第i个盘子里有多少个饼,每次操作可以将某个盘子的一个饼放到相邻盘子上,问你最少需要多少次操作次数才能让a数组非递增
这道题,这个模式,其实就很像DP,如果能够感觉出来的话,但是对于这道题的状态设置就会很难想,很难想的最大一个难点在于:如何确保满足数组非递增
我们可以这样:dp[i][j]表示前i-1个数前缀和为j的最少需要次数,然后遍历k,也就是在第i个位置有k大小的数的情况。
然后我们从大到小遍历k,再遍历i和j,从大到小遍历k就是为了保证数组非递增,然后注意,当前面i-1个数是j时,我需要的操作次数就是原本的前缀和跟j相减的绝对值
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define lowbit(x) x&(-x)
const int MOD=1e9+7;
const int N=1000100;
int a[N];
int pre[N];
int dp[255][255];
void solve() {
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i],pre[i] = pre[i-1] + a[i];
for(int i=0;i<=n;i++) {
for(int j=0;j<=m;j++) dp[i][j] = MOD;
}
dp[0][0]=0;
for(int k=m;k>=0;k--) {
for(int i=1;i<=n;i++) {
for(int j=0;j<=m-k;j++)
dp[i][j + k] = min(dp[i][j+k] ,dp[i-1][j] + abs(j - pre[i-1]) );
}
}
//for(int i=1;i<=n;i++) {
// for(int j=0;j<=m;j++) cout<<i<<" "<<j<<" "<<dp[i][j]<<"\n";
//}
cout<<dp[n][m]<<"\n";
}
signed main() {
//ios::sync_with_stdio(false);
//cin.tie(0);
//cout.tie(0);
//cout<<prime[cnt-1]<<"\n";
//for(int i=1;i<=cnt;i++) cout<<prime[i]<<"\n";
int t=1;
//cin>>t;
while(t--) {
solve();
}
return 0;
}