AtCoder Beginner Contest 215 题解(A-F)
A. Your First Judge
题目大意:
判断字符串是否等于 " H e l l o , W o r l d ! " "Hello,World!" "Hello,World!",完全匹配就输出 " A C " "AC" "AC",否则输出 " W A " "WA" "WA"。
解题思路:
签到题。
代码:
#include<bits/stdc++.h>
using namespace std;
int main()
{
string s;
cin>>s;
puts(s=="Hello,World!"?"AC":"WA");
return 0;
}
B. log2(N)
题目大意:
对于给出的 N N N求 ⌊ l o g 2 N ⌋ \left\lfloor log_2N\right\rfloor ⌊log2N⌋。
解题思路:
如果直接用C++中的 l o g 2 ( ) log2() log2()会存在精度问题,所以最好还是手动模拟一下。
代码:
#include<bits/stdc++.h>
using namespace std;
long long n;
int main()
{
cin>>n;
for(int i=0;;i++){
if((1ll<<i)>n){
cout<<i-1<<endl;
break;
}
}
return 0;
}
C. One More aab aba baa
题目大意:
对于给出的字符串的 S S S求出第 k k k小的字符串排列。
解题思路:
可以将整个字符串从小到大排序,得到最小的字符串排列,然后再跑 k − 1 k-1 k−1次next_permutaion()得到第 k k k小的排列。
代码:
#include<bits/stdc++.h>
using namespace std;
string s;
int k;
int main()
{
cin>>s>>k;
sort(s.begin(),s.end());
for(int i=0;i<k-1;i++) next_permutation(s.begin(),s.end());
cout<<s<<endl;
return 0;
}
D. Coprime 2
题目大意:
给出 N N N个整数 A 1 , A 2 , . . . , A N A_1,A_2,...,A_N A1,A2,...,AN,找出所有在 [ 1 , M ] [1,M] [1,M]中满足以下条件的整数 k k k:
- g c d ( k , A i ) = 1 , 1 ≤ i ≤ N gcd(k,A_i)=1,1\le i \le N gcd(k,Ai)=1,1≤i≤N。
解题思路:
可以通过将这 N N N个整数的质因数全部提取出来,然后用类似埃氏筛的思路将 [ 1 , M ] [1,M] [1,M]区间中这些质因数的倍数全部筛去,也可以把这些质因数全部标记之后在枚举 [ 1 , M ] [1,M] [1,M]所有整数的过程中判断每个整数是否有这些因数。
时间复杂度分别是 O ( N N + N l o g N ) O(N\sqrt N+NlogN) O(NN+NlogN)和 O ( 2 N N ) O(2N\sqrt N) O(2NN)。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e5+10;
int a[N];
int n,m;
bool vis[N];
void check(int x){
for(int i=2;i<=x/i;i++){
if(x%i==0){
vis[i]=true;
while(x%i==0) x/=i;
}
}
if(x>1) vis[x]=true;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
check(a[i]);
}
vector<int> res;
for(int i=1;i<=m;i++){
bool flag=true;
for(int j=1;j<=i/j;j++){
if(i%j==0&&(vis[i/j]||vis[j])){
flag=false;
break;
}
}
if(flag) res.push_back(i);
}
printf("%d\n",res.size());
for(auto i :res) printf("%d\n",i);
return 0;
}
E. Chain Contestant
题目大意:
给出一个长度为 N N N的字符串,字符串由 A − J A-J A−J十种大写英文字母组成。问有多少种不同的子序列满足子序列中相同的字母紧挨在一起,即所有相同的字母都是连续的,答案对 998244353 998244353 998244353取模。
解题思路:
因为字符串中只有十种字母,所以我们可以考虑用二进制来表示状态中的字母组成。
设 f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示考虑前 i i i个字母,当前字母组成状态为 j j j且以 k k k结尾的状态的总和。
接下里考虑状态转移:
- 考虑接到之前某个子序列的后面
- 如果之前的子序列就是以 k k k结尾的,很显然可以接在后面,此时 f [ i ] [ j ] [ k ] = ∑ u = 1 i − 1 f [ u ] [ j ] [ k ] f[i][j][k]=\sum \limits_{u=1}^{i-1} f[u][j][k] f[i][j][k]=u=1∑i−1f[u][j][k]。
- 如果之前的子序列不是以 k k k结尾的,如果 k k k在子序列并没有出现,我们同样可以接在后面,可以求得能转移过来的子序列的状态为 p r e = j − ( 1 < < k ) pre=j-(1<<k) pre=j−(1<<k),此时 f [ i ] [ j ] [ k ] = ∑ u = 1 i − 1 ∑ y = 0 9 f [ u ] [ p r e ] [ y ] f[i][j][k]=\sum\limits_{u=1}^{i-1}\sum\limits_{y=0}^9 f[u][pre][y] f[i][j][k]=u=1∑i−1y=0∑9f[u][pre][y]。
- 除了接在某个子序列的后面,还可以将这个字母单独作为一个新的子序列,此时 f [ i ] [ 1 < < k ] [ k ] + = 1 f[i][1<<k][k]+=1 f[i][1<<k][k]+=1
时间复杂度 O ( 10 × n × 2 10 ) O(10\times n\times2^{10}) O(10×n×210)
因为状态转移可以看成是从之前所有状态中转移过来,并不要求对状态分层,所以可以考虑压缩一维。
如果 d p dp dp比较熟练的话很容易想到直接用二维来做,为了讲的清晰一点这里就用三维的状态来解释, A t c o d e r Atcoder Atcoder的官方题解用的三维,并不会超空间,所以选择自己习惯的方式就行。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1100,mod=998244353;
int f[N][10];
char s[N];
int n,a[N];
int main()
{
scanf("%d",&n);
scanf("%s",s+1);
for(int i=1;i<=n;i++) a[i]=s[i]-'A';
for(int i=1;i<=n;i++){
int x=a[i];
for(int j=0;j<1<<10;j++){
if((j&(1<<x))==0) continue;
f[j][x]=(f[j][x]+f[j][x])%mod;
int k=j-(1<<x);
for(int y=0;y<10;y++){
f[j][x]=(f[j][x]+f[k][y])%mod;
}
}
f[1<<x][x]=(f[1<<x][x]+1)%mod;
}
int res=0;
for(int i=0;i<1<<10;i++)
for(int j=0;j<10;j++) res=(res+f[i][j])%mod;
printf("%d\n",res);
return 0;
}
F. Dist Max 2
题目大意:
给出 N N N个二维平面的点,定义两个点 ( x i , y i ) , ( x j , y j ) (x_i,y_i),(x_j,y_j) (xi,yi),(xj,yj)之间的距离为 min ( ∣ x i − x j ∣ , ∣ y i − y j ∣ ) \min(|x_i-x_j|,|y_i-y_j|) min(∣xi−xj∣,∣yi−yj∣),问所有点中两个点之间的最大距离是多少。
- 2 ≤ N ≤ 200000 2\le N\le 200000 2≤N≤200000
- 0 ≤ x i , y i ≤ 1 0 9 0\le x_i,y_i\le 10^9 0≤xi,yi≤109
解题思路:
在看题解之前是没想到能用二分做的,这道题check的方式很酷炫。
最大距离肯定是单调的,所以主要的思路是对 [ 0 , 1 0 9 ] [0,10^9] [0,109]这个区间进行二分。
如果这次我们要判断最大距离能否达到 m i d mid mid,因为 min ( ∣ x i − x j ∣ , ∣ y i − y j ∣ ) ≥ m i d → ∣ x i − x j ∣ ≥ m i d , ∣ y i − y j ∣ ≥ m i d \min(|x_i-x_j|,|y_i-y_j|)\ge mid\rightarrow|x_i-x_j|\ge mid,|y_i-y_j|\ge mid min(∣xi−xj∣,∣yi−yj∣)≥mid→∣xi−xj∣≥mid,∣yi−yj∣≥mid,所以我们可以维护一个集合中 y y y的最大值和最小值并且使得这个集合中最大的 x x x小于等于当前的 x i − m i d x_i-mid xi−mid。
所以可以想到通过先将所有点以 x x x作为第一关键词升序排序,每次check的时候用一个双指针来维护集合的最大值和最小值。
具体做法可以看代码,看完代码就很清晰明了了。
时间复杂度 O ( n l o g 1 0 9 ) O(nlog10^9) O(nlog109)。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N=2e5+10;
PII a[N];
int n;
bool check(int mid){
int Min=1e9,Max=0;
for(int i=1,j=1;i<=n;i++){
while(j<=n&&a[i].first>=a[j].first+mid){
Min=min(Min,a[j].second);
Max=max(Max,a[j].second);
j++;
}
if(a[i].second<=Max-mid||a[i].second>=Min+mid) return true;
}
return false;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d%d",&a[i].first,&a[i].second);
sort(a+1,a+1+n);
int l=0,r=1e9;
while(l<r){
int mid=l+r+1>>1;
if(check(mid)) l=mid;
else r=mid-1;
}
printf("%d\n",r);
return 0;
}