A. Make Even
题意:给一个数字,每次可以选择一段前缀反转,问最少几次反转能变成偶数
题解:如果一开始是偶数就是0,如果首位是偶数就是1,如果中间存在一位上的数是偶数那么则是2(先翻转一次将偶数转到首位),如果全都是奇数则不可能
#include <bits/stdc++.h>
using namespace std;
int t,n;
signed main(){
cin>>t;
while(t--){
cin>>n;
int ans1=0,ans2=0;
if(n%2==0){
cout<<0<<endl;
continue;
}
else{
while(n){
if(n&1)ans1++;
ans2++;
if(n/10==0&&n%2==0){
cout<<1<<endl;
break;
}
n/=10;
}
if(n)continue;
if(ans1==ans2){
cout<<-1<<endl;
}
else{
cout<<2<<endl;
}
}
}
return 0;
}
B. Team Composition: Programmers and Mathematicians
题意:有A类人员a个,B类人员b个,组成四人小队且一定需要两种人员都有,请问最多组成多少个
题解:如果人数少一类的三倍少于另一类,则一定是1:3配比组完所有队,否则一定能贪心的组完所有队伍
#include <bits/stdc++.h>
#define int long long
using namespace std;
int t,n,m;
void solve(){
cin>>n>>m;
if(n<m)swap(n,m);
if(m*3<=n){
cout<<m<<endl;
}
else {
cout<<(n+m)/4<<endl;
}
}
signed main(){
cin>>t;
while(t--)
solve();
return 0;
}
C. Polycarp Recovers the Permutation
题意:一个数组是一个1到n的排列,我们对其进行n轮操作会形成一个新的数组
取数组左右端两个数,将较小的数取出,并放到新数组的相应位置
现在有一个通过这么操作得到的新数组,求一个满足条件的原数组。
题解:首先,最大的数一定是最后放,所以新数组两端一定有一个值是n。接着构造原数组,不妨思考最大值n在原数组一段的情形,此时容易模拟出处n外所有数会反向,而最后一个n既可放左也可放右,则当n在原数组一端的时候,可以使整段数组完全反向。
所以我们只需将a数组倒置并输出即可。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int t,n,m;
int a[200005];
void solve(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
if(a[1]==n||a[n]==n){
reverse(a+1,a+1+n);
for(int i=1;i<=n;i++){
cout<<a[i]<<" ";
}
cout<<endl;
}
else cout<<-1<<"\n";
}
signed main(){
cin>>t;
while(t--)
solve();
return 0;
}
D. Weights Assignment For Tree Edges
题意:给定一个n个结点的树,再给一个1到n的排列,满足根到这个排列里的点的距离依次增加,询问询问如果存在那么每个边的边权是多少,不存在这样的树则输出-1(边权只能是正整数)
题解:
不妨令到排列中第i个点的权值是i,那么对于每个点和其父亲连边的权值即为它与其父亲权值的差值。
若遍历到某点时,其父亲还未赋值过,即其父亲权值大于他自己,边权为负数,则直接输出-1即可。
最后考虑根节点,显然根节点只能位于开头,特判开头是否是根节点即可。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int t,n,m;
int a[200005],p[200005],ans[200005];
void solve(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++){
cin>>p[i];
ans[i]=0;
}
if(a[p[1]]!=p[1]) {
cout<<-1<<endl;
return;
}
ans[p[1]]=1;
for(int i=2;i<=n;i++){
if(!ans[a[p[i]]]){
cout<<-1<<endl;
return;
}
ans[p[i]]=i;
}
for(int i=1;i<=n;i++)cout<<ans[i]-ans[a[i]]<<" ";
cout<<endl;
}
signed main(){
cin>>t;
while(t--)
solve();
return 0;
}
E1. Escape The Maze (easy version)
题意:一棵n个结点,以1为根的树,有m个鬼,人站在1号点,如果人安全跑到一个叶子节点则获胜,每个人和鬼每回合都能移动1格,请问人能否获胜
题解:考虑鬼的最优策略,即每次往自己父亲结点走,证明过程暂略。而显然,每点的深度即为根结点到该点的距离。由此,对每个结点即可考虑其所有子树所有结点中,最近的鬼里该结点的距离与该点深度比较,如果这个点深度小于鬼到该点距离,则可安全通过这个点。
对每个点存在安全通过该点且子树中存在一条路可逃跑则可通过该结点逃跑。
跑一遍dfs即可
(思路类似2020CCPC秦皇岛K)
#include <bits/stdc++.h>
#define int long long
using namespace std;
int t,n,m,k;
int a[200005],ans[200005];
int x[200005],vis[200005];
int u,v;
vector <int> p[200005];
void init(){
for(int i=1;i<=n;i++){
p[i].clear();
vis[i]=0;
ans[i]=10000000;
}
}
bool dfs(int u,int dep){
vis[u]=1;
//cout<<u<<endl;
int len=p[u].size();
int flag=0;
if(u!=1&&len==1&&ans[u]) return true;
for(int i=0;i<len;i++){
int v=p[u][i];
if(!vis[v]){
if(dfs(v,dep+1)) flag=1;
ans[u]=min(ans[u],ans[v]+1);
}
}
//cout<<flag<<endl;
if(dep<ans[u]&&flag)return true;
else return false;
}
void solve(){
cin>>n>>k;
init();
for(int i=1;i<=k;i++){
cin>>m;
ans[m]=0;
}
for(int i=1;i<n;i++){
cin>>u>>v;
p[u].push_back(v);
p[v].push_back(u);
}
if(dfs(1,0))cout<<"YES\n";
else cout<<"NO\n";
}
signed main(){
cin>>t;
while(t--)
solve();
}
E2. Escape The Maze (hard version)
题意:题意:一棵n个结点,以1为根的树,有m个鬼,人站在1号点,如果人安全跑到一个叶子节点则获胜,每个人和鬼每回合都能移动1格,请问最少多少鬼能获胜,所有鬼上场都抓不到人,则输出-1
题解:与上题相反,输出鬼获胜的条件。最优策略同上题。
同样对每个结点即可考虑其所有子树所有结点中,最近的鬼里该结点的距离与该点深度比较,如果这个点深度大于等于鬼到该点距离,则只需一个鬼即可守护这条路。否则,守护该点所需的人数为该点的所有子树所需守护人数之和。若该点存在一个子树无法守住,则该点无法守住。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int t,n,m,k;
int a[200005],ans[200005];
int x[200005],vis[200005];
int u,v;
vector <int> p[200005];
void init(){
for(int i=1;i<=n;i++){
p[i].clear();
vis[i]=0;
ans[i]=10000000;
}
}
int dfs(int u,int dep){
vis[u]=1;
//cout<<u<<endl;
int sum1=0,sum2=0;
int len=p[u].size();
int flag=0;
if(u!=1&&len==1&&ans[u]) return -1;
for(int i=0;i<len;i++){
int v=p[u][i];
if(!vis[v]){
sum2=dfs(v,dep+1);
if(sum2==-1) flag=1;
else sum1+=sum2;
ans[u]=min(ans[u],ans[v]+1);
}
}
//cout<<flag<<endl;
if(dep<ans[u]&&flag)return -1;
else if(dep>=ans[u])return 1;
else if(flag==0) return sum1;
}
void solve(){
cin>>n>>k;
init();
for(int i=1;i<=k;i++){
cin>>m;
ans[m]=0;
}
for(int i=1;i<n;i++){
cin>>u>>v;
p[u].push_back(v);
p[v].push_back(u);
}
cout<<dfs(1,0)<<endl;
}
signed main(){
cin>>t;
while(t--)
solve();
return 0;
}
F. ATM and Students
题意:现在银行有n个人要处理业务,一开始有s元钱,每个人可能存钱或取钱,正数表示存钱,负数表示取钱,银行可以选择从某个人开始连续处理业务,至多连续处理多少人的业务,输出这个区间。如果一个人都无法处理则输出-1;
题解:读完题发现询问最长区间,满足所有前缀的前缀和大于等于-s,显然二分答案+st表可暴力写。复杂度为O(nlogn)。
考虑双指针,对某一段(l,r)区间满足答案要求,则更新答案,跳右指针。
若跳完右指针不满足条件,则不断跳l直到满足条件。
证明过程比较繁琐,一句话证明即在跳左端点过程中(l,l`)中区间和为负,以其中任意一点为起点不会更优。
第一反应就是双指针板子题,但是看群友好像大部分写的都是st表+二分,大受震撼。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int t,n,m,k,l,r,ansl,ansr,sum,s;
int a[200005];
void init(){
ansl=ansr=-1,sum=0,l=r=1;
}
void update(){
if(r-l>ansr-ansl) ansr=r,ansl=l;
}
void solve(){
cin>>n>>s;
for(int i=1;i<=n;i++)cin>>a[i];
init();
while(l<=n&&r<=n+1){
if(sum+s>=0)update(),sum+=a[r++];
else sum-=a[l++];
}
if(ansr==-1) cout<<-1<<endl;
else cout<<ansl<<" "<<ansr-1<<endl;
}
signed main(){
cin>>t;
while(t--)
solve();
}