【赛时总结】◇赛时·V◇ Codeforces Round #486 Div3

时间:2021-10-01 11:21:39

◇赛时·V◇ Codeforces Round #486 Div3

又是一场历史悠久的比赛,老师拉着我回来考古了……为了不抢了后面一些同学的排名,我没有做A题


◆ 题目&解析

【B题】Substrings Sort +传送门+   [暴力模拟]

  • 题意

给出n个字符串,你需要将它们排序,使得对于每一个字符串,它前面的字符串都是它的子串(对于字符串i,则字符串 1~i-1 都是它的子串)。

  • 解析

由于n最大才100,所以 O(n3) 的算法都不会爆,很容易想到暴力模拟。

如果字符串i是字符串j的子串,则(len是长度)len[i]≤len[j]。所以我们可以把字符串按长度从小到大排序,然后n2判断每一个字符串前面的字符串是不是它的子串,如果都是,则直接输出,否则就是"NO"。

这里找子串我用的是 string 的 substr 取子串,对于字符串 i,j (len[j]≤len[i] && i≠j),找 i 中长度为 len[j] 的子串,如果有一个子串等于 j ,则 j 是 i 的子串。

我了解到还有一个 find 函数,可以直接找,但是不会用啊 ヽ(╯▽╰)ノ

  • 源代码
 /*Lucky_Glass*/
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
const int MAXN=;
int n,len[MAXN+];
string str[MAXN+];
bool cmp(string A,string B){return A.length()<B.length();}
int main()
{
scanf("%d",&n);
for(int i=;i<n;i++)
cin>>str[i];
sort(str,str+n,cmp);
for(int i=;i<n;i++)
len[i]=str[i].length();
for(int i=n-;i>=;i--)
for(int j=i-;j>=;j--)
{
bool flag=false;
for(int k=;k<=len[i]-len[j];k++)
{
if(str[i].substr(k,len[j])==str[j])
{
flag=true;
break;
}
}
if(!flag) {printf("NO\n");return ;}
}
printf("YES\n");
for(int i=;i<n;i++)
cout<<str[i]<<endl;
return ;
}

【C题】Equal Sums +传送门+  [STL实现]

  • 题意

给出n个序列(序列长度以及序列元素),你要从中挑选出两个序列,并在两个序列中分别删除一个数,使得删除后两序列元素之和相等。输出你挑选的序列以及你删除的是该序列中的第几个元素。如果无法找到满足条件的序列,输出"-1"。

  • 解析

这道题最可怕的是它的数据规模,根本存不下……因此我们要学会在线处理。

所谓在线处理就是一边输入一边计算答案。我们可以发现,其实它的序列之和非常小,105是数组存得下的,于是想到储存减去一个元素的和的方法。

我们可以储存它当前输入的序列(谢天谢地),所以我们可以求出它的元素之和与每个元素之差。这里就可以用一个 "map<int,pair<int,int> >" 也就是把一个整数映射到一个整数对上。这里的整数是序列之和与它的一个元素的差,整数对就是{序列的编号,序列中被删除的元素编号}。先计算出当前序列的和sum,再枚举它的每一个元素i,计算总和减去元素i的值x,如果发现映射已经中有一个值等于x,则说明之前有一个序列总和减去它的某一个元素等于当前序列减去元素i了(也就是满足题目的条件),这个时候就可以储存答案,并标记答案已经找到。然后把当前的每一个值存入映射中(不需要担心会把之前的映射值覆盖,反正都是输出任意解),要注意不能一边查找映射中是否存在答案,一边将当前序列的值存入映射中,因为这样可能找到同一个序列!

  • 源代码
 /*Lucky_Glass*/
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<map>
using namespace std;
int QuickRead(int &x)
{
int a=,b=;char c=getchar();
while(!(''<=c && c<='')) b=c=='-'?-:b,c=getchar();
while(''<=c && c<='') a=a*+c-'',c=getchar();
return x=a*b;
}
const int MAXN=int(2e5);
int n,num[MAXN+];
map<int,pair<int,int> > vis;
int main()
{
bool flag=false;
scanf("%d",&n);
for(int i=;i<n;i++)
{
int len,sum=;QuickRead(len);
for(int j=;j<len;j++) sum+=QuickRead(num[j]);
if(flag) continue;
for(int j=;j<len;j++)
if(vis.count(sum-num[j]))
{
flag=true;
printf("YES\n");
printf("%d %d\n",vis[sum-num[j]].first,vis[sum-num[j]].second);
printf("%d %d\n",i+,j+);
break;
}
if(flag) continue;
for(int j=;j<len;j++)
vis[sum-num[j]]=make_pair(i+,j+);
}
if(!flag) printf("NO\n");
return ;
}

【D题】 Points and Powers of Two +传送门+  [数学推导]

  • 题意

给出n个数,你需要从中选出尽量多的数加入集合 A,使得集合 A 中的每个元素与其他元素的差的绝对值为2的幂,输出集合A的大小,并输出集合A(多解输出任意一个)。

  • 解析

设集合A为 {a1,a2,...ap} (ai<ai+1),且 ai+1 - ai=2x[i](1≤i<p)。则 ai+2 - a= 2x[i] + 2x[i+1],而我们知道 ai+2 - ai 也是2的幂,什么时候 2x[i] + 2x[i+1] 是2的幂呢?其实就是 x[i]=x[i+1] 的时候!所以我们可以得到集合A中相邻元素的差是相等的。又因为差相等,所以集合A最多有3个元素,且都不相等(=_= 自己找个数据试试吧)。

我们可以用set储存n个数中出现的数,然后枚举一个数 num[i],再枚举2的幂 2j,然后再枚举集合A的长度 k,判断set中(即n个数中)是否有k个元素满足相邻之差为 2j,若有,则更新答案 。

注意:如果没有连续的元素满足上述条件,则输出n个数的任意一个(因为一个数没有差)。

  • 源代码
 /*Lucky_Glass*/
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
using namespace std;
const int MAXN=int(2e5);
int n;
long long num[MAXN+];
set<long long> vis;
vector<long long> _2;
vector<long long> ans;
long long ANS;
int main()
{
long long X=;
while(X<=2e9)
_2.push_back(X),X*=;
_2.push_back(X);
scanf("%d",&n);
for(int i=;i<n;i++)
scanf("%lld",&num[i]),vis.insert(num[i]);
for(int i=;i<n;i++)
for(int j=;j<_2.size();j++)
for(int k=;k<=;k++)
if(vis.count(num[i]+k*_2[j]))
{
int f=k+;
if(f>ans.size())
{
ans.clear();ANS=j;
for(int g=;g<f;g++)
ans.push_back(num[i]+g*_2[j]);
}
}
else
break;
if(ans.empty())
{
printf("%d\n%lld\n",,num[]);
}
else
{
printf("%d\n",ans.size());
for(int i=;i<ans.size();i++)
i? printf(" %lld",ans[i]):printf("%lld",ans[i]);
printf("\n");
}
return ;
}

【E题】 Divisibility by 25 +传送门+  [代码实现]

  • 题意

给出一个数n,你可以选择n的相邻两个数位对其进行交换,最后使得n能够被25整除,但是在每一次交换后不能够出现前导零(输入时保证没有前导零)。如果可以实现,则输出最少需要交换多少次,反正输出-1。

  • 解析

25是一个 special 的数字,它的倍数末尾两位为 00,25,50或75 。所以我们可以把问题转换为——交换n的相邻两位,使得n的末尾为 00,25,50或75。

四种情况不多,可以分类讨论,计算每一种情况的答案取最小值。我们可以直接用字符串存数n。

写一个 Find(x,sta) 表示n中从第sta个数位开始,数字x第一次出现的位置(没有出现返回-1)。我们可以找到 0,2,5,7 在n中第一次出现的位置。

先是00的情况。我们再找一遍第一个0的后面还有没有0(也就是有没有两个0),然后将离个位近的0移到个位,另一个移到十位,因为数据保证没有前导零,所以这种情况不需要防止前导零以及两个数字的位置。

然后判断25的情况。首先确定2,5是否都存在,则将5移到个位,2移到十位,需要考虑前导零、两个数字最初的位置。50,75 与 25相同。

下面是关于 25,50,75 的一些细节:

如果两个数字最初的先后顺序与我们要求的不同(比如25的情况,最初5在2前面)。则两个数字一定有一次操作使他们互换,使得他们顺序正确。所以操作数加1。

对于前导零,我们先找到除开我们需要移动的两个位置的数的不为零的数的位置(比如52102中找25,找到的第一个不为零的数为2(千位))。如果在第一个0出现之前,则不用管,否则把它移到第一个0前面,再将目标移到末尾。(详见Zero()函数)

  • 源代码
 /*Lucky_Glass*/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int INF=int(1e9);
char str[];
int len,ans=INF;
int pos[];
int Find(char x,int sta)
{
for(int i=sta;i>=;i--)
if(str[i]==x)
return i;
return -;
}
int Zero(int A,int B)
{
if(len==) return ;
int cnt=;
for(int i=;i<=len;i++)
{
if(str[i]!='' && A!=i && B!=i)
return i--cnt;
if(i==A || i==B) cnt++;
}
return INF+;
}
int main()
{
scanf("%s",str+);
len=strlen(str+);
pos[]=Find('',len);
pos[]=Find('',len);
pos[]=Find('',len);
pos[]=Find('',len);
if(pos[]!=- && (pos[]=Find('',pos[]-))!=-)
ans=min(ans,len--pos[]+len-pos[]);
if(pos[]!=- && pos[]!=-)
ans=min(ans,len-pos[]+len--pos[]+(pos[]>pos[])+Zero(pos[],pos[]));
if(pos[]!=- && pos[]!=-)
ans=min(ans,len-pos[]+len--pos[]+(pos[]>pos[])+Zero(pos[],pos[]));
if(pos[]!=- && pos[]!=-)
ans=min(ans,len-pos[]+len--pos[]+(pos[]>pos[])+Zero(pos[],pos[]));
printf("%d\n",ans==INF? -:ans);
return ;
}

【F题】 Rain and Umbrellas +传送门+  [动态规划]

  • 题意

你在一条数轴上的0点,需要走到a点。在0~a的位置中,有n个区间在下雨(保证区间不相交),经过这些区间时必须打伞。有m个位置放了伞(同一个位置可能有多把伞),伞有不同的重量,你可以捡起这把伞,而你每行走一个单位长度,你花费的体力是你捡起的伞的重量。你可以在任意处扔下伞(除非你正在下雨的区间内),且可以瞬间丢下伞或者拿起伞。求你从0走到a的最小花费体力。

  • 解析

这道题考虑的东西比较多,肯定是动态规划。而且区间不多,区间总长度也不大,连离散化也不用。

定义 dp[i] 为走到 i 时的最小花费体力,rain[i] 表示i位置是否在下雨,unb[i] 表示 i 位置的伞的最小重量(没有伞则赋值为极大值)。

如果第 i-1 个位置没有下雨,则我们可以延续第 i-1 个位置的状态,即 rain[i-1]==false 时,dp[i]=dp[i-1];否则我们往前找到一个有伞的位置j,设我们在j处拿上(或者换上伞),然后走到i处,则走了(i-j)个单位距离,便可以计算 j->i 的体力花费,即 dp[i]=min{dp[j]+(i-j)*unb[j]} 。

最后答案即是 dp[a]。

注意这里描述的下雨的区间其实是一个左开右闭区间,样例1如下图:

【赛时总结】◇赛时·V◇ Codeforces Round #486 Div3

也就是说i~i+1的雨可以看成i点的雨,也就压缩成了左开右闭区间。

  • 源代码
 /*Lucky_Glass*/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXM=;
typedef long long ll;
int goal,n_rain,n_unb;
bool rain[MAXM+];
int unb[MAXM+];
ll dp[MAXM+];
int main()
{
scanf("%d%d%d",&goal,&n_rain,&n_unb);
fill(dp,dp+MAXM+,(ll)1e18);
fill(unb,unb+MAXM+,(int)1e9);
for(int i=;i<n_rain;i++)
{
int l,r;scanf("%d%d",&l,&r);
if(l>r) swap(l,r);
for(int j=l;j<r;j++)
rain[j]=true;
}
for(int i=;i<n_unb;i++)
{
int pos,wgt;scanf("%d%d",&pos,&wgt);
unb[pos]=min(unb[pos],wgt);
}
dp[]=;
for(int i=;i<=goal;i++)
if(!rain[i-])
dp[i]=dp[i-];
else
for(int j=i-;j>=;j--)
if(unb[j]!=1e9)
dp[i]=min(dp[i],dp[j]+(i-j)*unb[j]);
if(dp[goal]==1e18) printf("-1\n");
else printf("%lld\n",dp[goal]);
return ;
}

The End

Thanks for reading!

- Lucky_Glass

(Tab:如果我有没讲清楚的地方可以直接在邮箱lucky_glass@foxmail.com email我,在周末我会尽量解答并完善博客~)