前言:
非ACM强校,非科班,大二,纯兴趣无OI经验,8月前仅上过C语言课,8月开始学习部分算法知识,10月第一场codeforces比赛,div3,即本场,一题没有做出来,有些失望,慢慢成长。
**我一定能够成为我想要去成为的人。**
A. Elections
- 题目大意:
输入三个数,分别求出使这三个数变为三个数中最大数的最小加数。例如输入1 3 4,输出就是4 2 0。 - 题型分类与依据:
分类:简单思维题,但要注意不同分数情况选手的处理分类。
依据:凭感觉就知道是水题的水题。 - 解题思路:
要加最少的分,变成分数最高的,分两种情况讨论:①如果我们目前不是分数最高的,那么我们的目标分数,就是最高分选手的分数+1,那么就是我们目前的分数,加上我们与最高分选手之间相差的分数,再加1;②如果我们目前分数是最高的,我们不用加分,我们就是最高的,那么我们的最少加分就是零。 - 代码实现:
#include<bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
int t,a,b,c,maxa,maxb,maxc;
cin>>t;
while(t--){
cin>>a>>b>>c;
if(a>max(b,c)){
maxa=0;
}
else
maxa=max(a,max(b,c))-a+1;
if(b>max(a,c)){
maxb=0;
}
else
maxb=max(a,max(b,c))-b+1;
if(c>max(a,b)){
maxc=0;
}
else
maxc=max(a,max(b,c))-c+1;
cout<<maxa<<" "<<maxb<<" "<<maxc<<endl;
}
return 0;
}
B.Make it Divisible by 25
- 题目大意
输入一串数字(大于等于25),已知该数字串在经过删掉某几位上的数字后,能被25整除,求最少删掉几个数字,能实现被25整除。如100最少删0个数字,3295最少删1个数字9,删3和9也是可以的,但不是最少,我们要求最少。 - 题型判断及依据
题型:数论、深搜DFS、贪心
判断依据:数论——数字,被25整除,整除问题一般牵涉数论;深搜DFS、动态规划DP——通过一步一步(每次删一个数字)达到极值状态(最少),一般牵涉贪心、BFS、DFS、DP。 - 解题思路
整除问题一般考虑尾数特征,一般都会有不变的尾数规律特征。题目是说能被25整除,能被25整除的数字,后两位数字的组成可能组合,有且仅有4种,分别为:25、50、75、00。那如果题目改成20呢?很明显,也是一样的,有且仅有20、40、60、80、00这5种组合情况。这就是整除问题的习惯性套路。
知道了这个特征,这个问题就从如何变成能被25整除的数,转化为如何变为尾数为25、50、75、00四种组合中的任意一种情况。
我们再仔细看看这四种组合,发现了尾数要么是5,要么是0。也就是说,答案的尾数要么是5,要么是0。那么我们如何最快地删除以实现呢?思路很好想,采用贪心策略,寻找离末尾最近的0或5,把它后面的数全删了,就可以最快地删除以实现末尾是0或5了,而后面的数字数量,就是需要删除的数字数量。
但是,我们还要考虑,尾数是5的情况下,5前面的2或7如何最快删除地得来;尾数是0的情况下,0前面的0或5如何最快删除地得来?思路和刚才一致,继续采用贪心策略,如果尾数为5,从后往前找离这个尾数5前面最近的2或7;如果尾数为0,从后往前找离这个尾数0前面最近的0或5。中间所间隔的数字数量,就是需要删除的数字数量。
最后,将构造2、5尾数时后面的数字数量,加上2、5前面离他最近的匹配数字之间的数字数量,就是构造出一个能被25整除的数字所需要删除的数字数量,我们只需要遍历所有情况,找出所有可构造的删除数字量中的最小值即可。(DFS方法也可以做,但是可能本人TLE了,暂时未破,所以先用贪心+数论的常规解题思路讲解)。 - 代码实现
#include<bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
int t;
string s;
cin>>t;
while(t--){
int ans=999999;
cin>>s;
int cnt=0,d=0;
for(int i=s.size()-1;i>=0;i--){
if(s[i]=='5'){
for(int j=i-1;j>=0;j--){
if(s[j]=='2'||s[j]=='7'){
ans=min(cnt+d,ans);
break;
}
d++;
}
}
cnt++;
}
cnt=0,d=0;
for(int i=s.size()-1;i>=0;i--){
if(s[i]=='0'){
for(int j=i-1;j>=0;j--){
if(s[j]=='5'||s[j]=='0'){
ans=min(cnt+d,ans);
break;
}
d++;
}
}
cnt++;
}
cout<<ans<<endl;
}
return 0;
}
C.Save more mice
- 题目大意
在一维坐标轴中,猫位于原点,坐标为0,老鼠洞位于终点,坐标为n,在猫与老鼠洞之间,有k只老鼠。老鼠到达老鼠洞 ,就安全,猫到达老鼠的位置,老鼠就算死亡。每一次,可以选择其中某一只老鼠向右移动一个单位,然后猫会自动向右移动一个单位,然后再选任意一只老鼠向右移动一个单位,然后猫再向右移动一个单位。移动的原则是,老鼠第一个移动。请问,k只老鼠中,最多可以有几支老鼠安全存活? - 题型分类与依据
题型:贪心
依据:题目中有“最多”,这一明显的提示词存在。 - 解题思路
判断出来是贪心,那么贪心题型中最重要的思路就是“排序”优先处理。k支老鼠,我们肯定是先救最好救的,再救次好救的,依次类推,最后再救最难救的。每救完一只老鼠,猫就会移动所救老鼠距离老鼠洞的相同距离。途径中的老鼠都会死亡,当猫走到老鼠洞的时候,除了被救的老鼠,剩余的老鼠全死了,也就结束了。
因此我们的思路,就是sort函数对各个老鼠的位置进行降序排序,优先处理离老鼠洞的老鼠,同时记录猫的总路程。当猫的总路程大于等于n时,最多能救的老鼠,就救完了。 - 代码实现
#include<bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
int t,n,k,a[400005];
cin>>t;
while(t--){
cin>>n>>k;
for(int i=0;i<k;i++){
cin>>a[i];
}
sort(a,a+k,greater<int>());
int s=n,cnt=0;
for(int i=0;i<k;i++){
s-=(n-a[i]);
if(s<=0)break;
cnt++;
}
cout<<cnt<<endl;
}
return 0;
}
D1.All the same
- 题目大意
给出n个数字,选定一个数字k,n个数字分别减去特定几次k,n个数字最终值相等,求能满足题目要求的k的最大值。如1 3 5,k=2,1减去0次k,3减去1次k,5减去2次k,最终变为1 1 1,3个数字全相等。 - 题型分类与依据
题型:数论中的比较隐蔽最大公因数问题。
依据:(猜测)与数论有关的最大值公共数,一般都是考察最大公因数,可能只是比较隐蔽。 - 解题思路
首先,一个问题,求1 2 4 6的k值,是否与求 2 3 5 7的k值是同一个问题?
很明显,是一样的。唯一不同的是,后者的最终的相等值比前者多1。
也就是说,影响这道题结果的,只有各个数之间的差值关系。
那我们不妨将这个问题简化,全部都变成0是最小数的序列。
以样例1:1 5 3 1 1 5为例,我们把它简化为0 4 2 0 0 4。
减去特定几次k,n个数字相等,继刚才那条,这个相等的数是可以任意选的,对把,那么我们就把这个相等的数选择为简化后的序列最小数0。
问题就变成了,0 4 2 0 0 4,我们选一个k,各个非零元素减去任意整数个k后,让每个非零元素都变成0,求k的最大值。
太多非零元素有点棘手,我们先从两个非零元素开始分析。
假设A和B是简化序列中唯一的两个非零元素,如果存在这个最大k,那么必然满足:
A-ak=0,B-bk=0,其中a,b为特定整数,代表A减去了特定a次k,B减去了特定b次k。
那么我们就有A=a*k,移项得A/k=a;同理可得B/k=b。a和b是两个正整数。其实a和b是多少不重要,但重要的是a和b都是整数,也就是说A和B能被k整除,即k是A的因数,也是B的因数,那么k就是A和B的公因数!
那么题目要求最大k,就是求简化序列中的非零元素序列中的最大公因数。
求多个数字的最大公因数的思路,也是比较常见而且显而易见的了,我们可以先求前两个数字的最大公因数,两个数字的最大公因数可以通过名为双下划线小写gcd的系统内置数学函数实现,也可以用辗转相除法自定义一个gcd函数,先求前里个数字的最大公因数。求出前两个数字的最大公因数后,用该最大公因数与序列中其它的非零元素再求最大公因数,再求出一个新的最大公因数,再与其它的非零元素求最大公因数,直到最后,即是所有非零数字的最大公因数。 - 代码实现
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
ios::sync_with_stdio(false);
int t,n;
ll m,a[45];
cin>>t;
while(t--){
cin>>n;
ll mina=1e9;
for(int i=0;i<n;i++){
cin>>a[i];
mina=min(a[i],mina);
}
for(int i=0;i<n;i++){
a[i]-=mina;
}
sort(a,a+n,greater<int>());
ll m=__gcd(a[0],a[1]);
for(int i=1;i<n;i++){
if(a[i]!=0){
m=__gcd(m,a[i]);
}
}
if(m==0)m=-1;
cout<<m<<endl;
}
return 0;
}
D2.Half the same
- 题目大意
D1的基础上,求能够使得一半数字被减到相同的最大K。 - 题型分类
数论中的最大公因数问题,枚举,STL。 - 解题思路
求最大公因数,如果求所有数的最大公因数,显然前一题不断求两两之间的公因数的方法是最快的;但是如果是求部分数的最大公因数的最大值,遍历每个非零元素的最大满足因数来实现则更为明智。
我们来解释一下,什么叫通过遍历所有序列中某两元素差值的最大满足因数来实现。
先解释一下,为什么要求两元素差值。由D1知:A-ak=B-bk,因此我们有,A%k=B%k,A%k-B%k=0,(A-B)%k=(A%k-B%k)%k=0%k=0,(A-B)能够被k整除,故k一定是|A-B|中的整数。
接下来,我们枚举所有的差数对,实现所有差数的遍历。
例如我有一个序列, 3 6 7 9 12 15。那么,我们可以先找3和6 7 9 12 15的所有差数,并验证该差数,是否是至少n/2个元素的因数,如果是,我们就称之为“满足因数”,即满足D2题意的因数。如果3的满足因数有多个,我们只记录3的最大满足因数。类似地,接下来找6和剩下所有元素(7 9 12 15)的差数,依次类推,知道所有差数都被遍历过位置。在此过程中,不断更新比较最大满足因数,最后记录的即为最大的最大满足因数。
同时,我们需要先预处理输入数据已经有至少一半的相同数据的情况,此时k取任意值,都可以实现至少一半的数据相同,输出-1。 - 代码实现
#include<bits/stdc++.h>
using namespace std;
int a[50],n;
bool check(int num)
{
map<int,int> mp;
for (int i=1;i<=n;++i)mp[(a[i]%num+num)%num]++;
map<int,int>::iterator i;
for(i=mp.begin();i!=mp.end();i++) if(i->second >= n/2)return true;
return false;
}
int solve(int num)
{
int ans = 0;
for (int i=1;i<=num/i;++i)if (num%i==0)
{
if (check(i))ans = max(ans,i);
if (check(num/i))ans=max(ans,num/i);
}return ans;
}
int main()
{
ios::sync_with_stdio(false);
int t;cin>>t;
while (t--)
{
cin>>n;
for (int i=1;i<=n;++i)cin>>a[i];
map<int,int> mp;
map<int, int>::iterator i;
for (int i=1;i<=n;++i)mp[a[i]]++;
int ans = 0;
for(i = mp.begin(); i != mp.end(); ++i)if (i->second>=n/2)
{
ans= - 1;
break;
}if (ans==-1)
{
cout<<ans<<endl;
continue;
}
for (int i=1;i<=n;++i)
for (int j=i+1;j<=n;++j)
ans = max(ans,solve(abs(a[j]-a[i])));
cout<<ans<<endl;
}
}
E.Gardener and Tree
- 题目大意
N个点,N-1条边,每一次减去度数为1的点,问k次后,还剩多少个点?0个点,剪了还是0个点,只有一个点,剪了就0个点,只有两个点相连,剪了就0个点。 - 题型分类与依据
题型:BFS,数据结构,STL,图
依据:这题的解题思路,明显是树的层次遍历,遍历顺序是是从最底层(度数全为1),向上搜k-1层。同时,涉及存储图的数据的问题。图的存储,可以用邻接矩阵、邻接表,也可以用vector。 - 解题思路
首先存图,用vector创建一个动态数组,再创建一个度数数组,数组下标名为点名,数组内部值为这个点所关联的所有点的名字。每关联两个点,就将这两个点的度数数组值加一。
然后BFS,BFS第一步,队列类型是什么,取决于元素要包含哪些内容。根据题意,元素既要包含它是哪个点,即点的名字,也要包含,它的操作轮次,因为一旦到了第k轮,就不用再剪了。因此,我们可以用pair,也可以用结构体,创建一个新类型,包含这两个内容。
在队列内加入首元素,根据题意,首元素应该是度数小于等于1的所有点,那么就用for循环,遍历度数小于等于1的点加入队列,此时的轮次值,全部都是1。
考虑删除度数为1的点,如果首元素de数组为1,那么说明首元素这个点已经被剪掉了,就continue,访问队列中下一个元素点,如果de数组为0,那么就把首元素的点的de数组赋值为1,表示现在把这个点去掉的标志。
考虑题意,如果这个点所在的轮次,是第k轮,也不用继续操作了,也continue。
考虑次轮可能入队点选择,次轮可能入队列的点,应该是该点相连的点,用迭代器遍历该点所有的相连的点,由于当前点已经被剪去,所以遍历到的所有相连点的度数数组都减一,同时判断一下遍历的相连点能否入队列,判断依据还是,度数为小于等于1的点,就类似于刚才for循环挑选队列第一轮元素的值,都为度数小于等于1的点,除了度数小于等于1,我们还应该要注意,这个相连点是么有被剪过的,也就是它的de数组值为0,两个条件都满足就入队列,但是该相连点的操作轮次值要赋为当前点的操作轮次值+1,代表是当前点的下一轮操作.
遍历完所有当前点的相连元素,就进行第二轮的bfs搜索,直到第k轮,所有元素一直continue,所有元素都出列,队列为空,搜索结束。
最后,遍历de数组值,有多少个1,就说明有多少个点被剪了,那么总数n减去被减去的点数,就是所剩的点数,也就是最终答案。 - 代码实现
#include<bits/stdc++.h>
using namespace std;
const int maxn = 4e5+100;
vector<int> G[maxn];
bool de[maxn];
int du[maxn];
int n,k;
struct pos{
int dian,cnt;
};
int main()
{
ios::sync_with_stdio(0);
int T;cin>>T;
while (T--)
{
cin>>n>>k;
for (int i=1;i<=n;++i)G[i].clear(),du[i]=de[i]=0;
for (int i=1,u,v;i<n;++i)
{
cin>>u>>v;
G[u].push_back(v);
G[v].push_back(u);
du[u]++;du[v]++;
}
queue<pos> que;
for (int i=1;i<=n;++i)if (du[i]<=1)
que.push({i,1});
while (!que.empty())
{
pos p = que.front();
que.pop();
if (de[p.dian])continue;
de[p.dian]=1;
if (p.cnt==k)continue;
for (int v:G[p.dian])
{
du[v]--;
if (du[v]<=1&&!de[v])que.push({v,p.cnt+1});
}
}
int ans = n;
for (int i=1;i<=n;++i)if (de[i])--ans;
cout<<ans<<"\n";
}
}