前置
F 直接做除法导致精度炸掉,警钟敲烂。
[ABC324A] Same
题意简述
给定 n n n 个数,判断它们是否全部相同。
解题思路
直接判断即可。
代码示例
#include<bits/stdc++.h>
using namespace std;
int n,a[200010];
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
if(i!=1&&a[i]!=a[i-1]){
cout<<"No"<<endl;
return 0;
}
}
cout<<"Yes"<<endl;
return 0;
}
[ABC324B] 3-smooth Numbers
题意简述
判断一个数是否能表达成 2 x × 3 y 2^x \times 3^y 2x×3y 的形式。
解题思路
直接将这个数不断除以 2 2 2,再不断除以 3 3 3,结果为 1 1 1 则可以表示为这种形式,反之不行。
代码示例
#include<bits/stdc++.h>
using namespace std;
int n;
int main(){
cin>>n;
while(n%2==0) n/=2;
while(n%3==0) n/=3;
if(n==1) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
return 0;
}
[ABC324C] Error Correction
题意简述
给定 n n n 个字符串 s 1 , s 2 , … , s n s_1,s_2,\dots,s_n s1,s2,…,sn 和一个标准串 T T T,第一行输出有多少个字符串 s i s_i si 满足以下要求,第二行依次输出满足要求的字符串的编号。
∙ \bullet ∙ s i = T s_i=T si=T。
∙ \bullet ∙ s i s_i si 可以由 T T T 删除一个字符得到。
∙ \bullet ∙ s i s_i si 可以由 T T T 添加一个字符得到。
∙ \bullet ∙ s i s_i si 可以由 T T T 替换一个字符得到。
解题思路
一开始写了个 n l o g n n log n nlogn 不知道为什么 T 了,后来改成 O ( n ) O(n) O(n) 了。因为最多只能允许一个字符不一样(删除、添加或替换),所以其实可以直接扫过去判断(具体细节见代码)。
代码示例
#include<bits/stdc++.h>
using namespace std;
int n;
string x;
vector<int> ans;
bool check(string p){
if(p==x) return 1;
if(p.size()==x.size()){
int num=0;
for(int i=0;i<p.size();i++) num+=(p[i]!=x[i]);
if(num==1) return 1;
return 0;
}else if(p.size()==x.size()+1){
int num=0,cnt=0;
for(int i=0;i<p.size();i++){
if(p[i]!=x[cnt]){
num++;
continue;
}
cnt++;
}
if(num==1) return 1;
return 0;
}else if(p.size()==x.size()-1){
int num=0,cnt=0;
for(int i=0;i<x.size();i++){
if(x[i]!=p[cnt]){
num++;
continue;
}
cnt++;
}
if(num==1) return 1;
return 0;
}
return 0;
}
int main(){
cin>>n>>x;
for(int i=1;i<=n;i++){
string o;
cin>>o;
if(check(o)) ans.push_back(i);
}
cout<<ans.size()<<endl;
for(int i=0;i<ans.size();i++) cout<<ans[i]<<" ";
return 0;
}
[ABC324D] Square Permutation
题意简述
给定一个由数字构成的字符串 s s s,你可以交换 s s s 中任意两个位置的字符任意多次,问交换过程中最多会出现多少个不同的字符串,满足字符串所表示的十进制数(忽略前导0)是一个完全平方数。
解题思路
虽然有 n ≤ 13 n \leq 13 n≤13,然而直接进行排列仍然会 TLE。不妨从完全平方数入手,预处理出每个完全平方数各自包含多少个 1 1 1,多少个 2 2 2, … \dots …,多少个 9 9 9。这是因为交换两个字符不会改变字符本身。
最后还要注意, 0 0 0 出现的次数不定。即,若 s s s 中 0 0 0 出现 m m m 次,则 0 0 0 出现 0 0 0 至 m m m 次的情况都要考虑进去(这是因为,我们可以把 0 0 0 放到开头消除其对答案的影响)。
代码示例
#include<bits/stdc++.h>
using namespace std;
int n;
string s;
map<int,int> ans;
int main(){
cin>>n>>s;
for(int i=0;i<=3500000;i++){//3500000*3500000>9999999999999
int num=i*i;
int a[20];
for(int i=0;i<=9;i++) a[i]=0;
while(num) a[num%10]++,num/=10;
int cnt=1;
for(int i=0;i<=9;i++) cnt=cnt*10+a[i];
ans[cnt]++;//将0~9出现的次数状压进cnt中存起来
}
int num=0,Ans=0;
for(int i=0;i<s.size();i++) if(s[i]=='0') num++;
for(int i=0;i<=num;i++){//0可出现0~num次
multiset<int> x;
for(int i=0;i<s.size();i++) x.insert(s[i]-'0');
int cnt=1;
cnt=cnt*10+num-i;
for(int i=1;i<=9;i++) cnt=cnt*10+x.count(i);
Ans+=ans[cnt];//统计答案
}
cout<<Ans<<endl;
return 0;
}
[ABC324E] Joint Two Strings
题意简述
给定 n n n 个字符串 s 1 , s 2 , … , s n s_1,s_2,\dots,s_n s1,s2,…,sn,一个标准串 T T T。问有多少个 ( i , j ) (i,j) (i,j) 满足:
T T T 是 s i + s j s_i+s_j si+sj 的子串。
解题思路
处理出每个 s i s_i si 从后往前最多能匹配 T T T 多少位,然后处理出每个 s i s_i si 从前往后最多能匹配多少位。
若 s i s_i si 从前往后匹配的位数加上 s j s_j sj 从后往前匹配的位数大于等于 T T T 的长度,就一定是可行的。因为这时有重叠部分存在。对于每个 i i i,可以二分地寻找 j j j。具体细节见代码。
代码示例
#include<bits/stdc++.h>
using namespace std;
int n;
string s[500010],x;
int ans[500010];
int main(){
cin>>n>>x;
x=" "+x;
for(int i=1;i<=n;i++) cin>>s[i];
for(int i=1;i<=n;i++){
int num=x.size()-1;
for(int j=s[i].size()-1;j>=0;j--){
if(s[i][j]==x[num]) num--;
}
ans[i]=x.size()-1-num;//从后往前匹配
}
sort(ans+1,ans+n+1);
int Ans=0;
for(int i=1;i<=n;i++){
int num=1;
for(int j=0;j<s[i].size();j++){
if(s[i][j]==x[num]) num++;
}
num--;
int id=lower_bound(ans+1,ans+n+1,x.size()-1-num)-ans;
//二分找答案
Ans+=(n-id+1);
}
cout<<Ans<<endl;
return 0;
}
[ABC324F] Beautiful Path
题意简述
给定两个整数 n , m n,m n,m,随后 m m m 行每行四个参数 u , v , b , c u,v,b,c u,v,b,c,表示 u u u 到 v v v 有一条边(保证有 u ≤ v u\leq v u≤v ),这条边美丽值为 b b b,代价为 c c c。定义一条路径的美丽程度是这条路径上所有边的美丽值之和除以这条路径上所有边的美丽值之差。
问起点为 1 1 1,终点为 n n n 的路径中,美丽程度最大是多少?保证存在一条起点为 1 1 1,终点为 n n n 的路径。
解题思路
二分最大美丽程度。
一个很有用的条件是 u ≤ v u\leq v u≤v,这表明图中不存在环。而且我们可以通过从编号小的点遍历到编号大的点来计算最大值。因为当遍历到 i i i 节点时,指向 i i i 的边都在先前遍历过了。
需要注意的是不要直接用美丽值除以代价算,而是做个变形,这样就不会像我一样被卡精度了。
代码示例
#include<bits/stdc++.h>
using namespace std;
#define int long long
struct node{
int u;
double b,c,num;
bool operator<(const node &b)const{
return num>b.num;
}
};
int n,m;
double dis[200010];
vector<node> G[200010];
bool check(double num){
for(int i=1;i<=n;i++) dis[i]=-1000000000000;
dis[1]=0;//初始化
for(int i=1;i<=n;i++){
for(int j=0;j<G[i].size();j++){
dis[G[i][j].u]=max(dis[G[i][j].u],dis[i]+G[i][j].b-G[i][j].c*num);
//更新i节点能到的边。
//做了一个转换,没有直接用除法计算美丽程度
//而是根据当前二分的num判断符不符合
//其实就是把分母乘过来然后两边同时减去啦
}
}
return dis[n]>=0;
}
signed main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v,b,c;
cin>>u>>v>>b>>c;
G[u].push_back({v,b,c});
//单向边
}
double l=0,r=3000000000;
for(int i=0;i<=100;i++){
//浮点数二分模板
double mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
printf("%.10lf\n",l);
return 0;
}