AtCoder Beginner Contest 292 题解(A-Ex)
AtCoder Beginner Contest 292 题解(A-Ex)
- Link:Tasks - AtCoder Beginner Contest 292
- 难度评分:\(7-39-444-579-1,272-1,522-2,340-2,248\)
A - CAPS LOCK (Diff. 7)
简要题意:
给你一个只包含小写字母的字符串,将其中每一个字母改为对应的大写字母。
Sample:
Input:
abc
Output:
ABC
Solution:
考察字符串输入输出和 ASCII 码。
对于每一个字符,\(ch\) ,改为 \(ch-'a'+'A'\) 输出即可。
Code:
#include<bits/stdc++.h>
using namespace std;
signed main(){
string s;
cin>>s;
for(int i=0;i<s.size();i++) s[i]=s[i]-'a'+'A';
cout<<s;
}
B - Yellow and Red Card (Diff. 39)
简要题意:
有 \(n\) 个足球运动员,有 \(q\) 个询问,每个询问中会有一个运动员染黄或染红,或者查询给定的运动员是否被罚出场。(足球规则:两黄变一红,染红直接被罚出场)
输入中,第一行两个整数,分别表示 \(n,q\) 。
第二行到第 \((q+1)\) 行,每行两个整数 \(op,x\) 。
如果 \(op=1\) ,表示球员 \(x\) 领到黄牌。
如果 \(op=2\) ,表示球员 \(x\) 领到红牌。
如果 \(op=3\) ,表示询问球员 \(x\) ,是否被罚出场。
Sample:
Input:
3 9
3 1
3 2
1 2
2 1
3 1
3 2
1 2
3 2
3 3
Output:
No
No
Yes
No
Yes
No
Solution:
按照要求模拟即可,或者用些 trick ,比如可以设黄牌为一分,红牌为两分,满两分出场,记录每个球员的分数即可。
Code:
#include<bits/stdc++.h>
using namespace std;
int a[200010];
signed main(){
int n, q;
cin>>n>>q;
while(q--){
int op,x;
cin>>op>>x;
if(op==1){
a[x]++;
}
if(op==2) a[x]+=2;
if(op==3){
if(a[x]>=2){
cout<<"Yes"<<endl;
}else{
cout<<"No"<<endl;
}
}
}
}
C - Four Variable (Diff.444)
简要题意:
给你一个正整数 \(n\) ,问有多少中不同的四元组 \((A,B,C,D)\) 满足 \(AB+CD=n\) ?
Sample:
Input:
4
Output:
8
$ (A,B,C,D) $ として以下の $ 8 $ 個が考えられます。
- $ (A,B,C,D)=(1,1,1,3) $
- $ (A,B,C,D)=(1,1,3,1) $
- $ (A,B,C,D)=(1,2,1,2) $
- $ (A,B,C,D)=(1,2,2,1) $
- $ (A,B,C,D)=(1,3,1,1) $
- $(A,B,C,D)=(2,1,1,2) $
- $ (A,B,C,D)=(2,1,2,1) $
- $ (A,B,C,D)=(3,1,1,1) $
Solution:
考虑枚举 \(AB\) 的乘积,设 \(AB\) 的乘积为 \(i\) 则有:
令 \(dp[i]\) 表示两个正整数乘积为 \(i\) 的方案数。
考虑如何求 \(\{dp\}\) 数组,只需要暴力统计每个数的因数个数就可以了。
时间复杂度在不优化的情况下是 \(O\sqrt{n}\) 的。
Code:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int a[200010];
int dp[200010];
signed main(){
for(int i=1;i<=200000;i++){
for(int j=1;i*j<=200000;j++){
dp[i*j]++;
}
}
int n;
cin>>n;
int ans=0;
for(int i=1;i<n;i++){
ans=ans+dp[i]*dp[n-i];
}
cout<<ans<<endl;
}
D - Unicyclic Components(Diff.579)
简要题意:
给你一个无向图,判断是否满足其中每一个联通块的点数和边数均相等。
Sample:
Input:
5 5
1 2
2 3
3 4
3 5
1 5
Output:
Yes
图长这样,显然满足。
Solution:
用并查姬维护联通块,同时维护一下连通块内的点数边数即可。
只需要在合并的时候把信息都存在编号小的点上就好了。
并查姬板子。
Code:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int fa[201000];
int sz[201000];
int nd[200010];
int find(int x){
if(fa[x]==x) return x;
return fa[x]=find(fa[x]);
}
signed main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
fa[i]=i;
nd[i]=1;
}
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
u=find(u);
v=find(v);
if(u>v) swap(u,v);
if(u==v){
sz[u]++;
} else{
fa[v]=u;
nd[u]+=nd[v];
sz[u]+=sz[v];
sz[u]++;
}
}
bool tg=false;
for(int i=1;i<=n;i++){
if(fa[i]!=i) continue;
if(sz[i]!=nd[i]){
tg=true;
break;
}
}
if(tg==false) cout<<"Yes";
else cout<<"No";
}
下半篇章。
E - Transitivity (Diff.1,272)
简要题意:
给你一个有向图,问你至少在图中加多少条有向边,才能使图中任意满足 \(A\) 到 \(B\) ,\(B\) 到 \(C\) 都有直接边的点对 \((A,B,C)\) 均满足 \(A\) 到 \(C\) 也有之间边。
Sample:
Input:
4 3
2 4
3 1
4 3
Output:
3
增加的边:\(2 \rightarrow 3,2 \rightarrow 1,4\rightarrow 1\) 。
Solution:
考虑样例的情况。
点二与点四有边,点四与点三有边,所以点二连点三,之后类似的,点二也要连点一。
可以推广得到一个点通过路径可以到的所有点都要与它连边(逐步考虑可知)。
所以只需要统计每个点走路径能走到几个点就好了。
Code:
#include<bits/stdc++.h>
using namespace std;
vector<int> g[2010];
bool isv[2010];
int sum;
void dfs(int u,int fa){
isv[u]=true;
sum++;
for(auto x:g[u]){
if(isv[x]==true) continue;
dfs(x,u);
}
}
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
g[u].push_back(v);
}
int ans=0;
for(int i=1;i<=n;i++){
sum=0;
for(int j=1;j<=n;j++) isv[j]=false;
dfs(i,0);
ans+=sum-1-g[i].size();
}
cout<<ans<<endl;
return 0;
}
F - Regular Triangle Inside a Rectangle (Diff. 1,522)
简要题意:
给定一个矩形的长和宽,求这个矩形内最大正三角形的边长。
Sample:
Input:
1 1
Output:
1.03527618041008295791
构造方案如下,输出即为 \(\sqrt{6}-\sqrt{2}\) 的值。
Solution:
这题很有意思。
考虑最大的三正角形,发现它一定有一个点在矩形的顶点上。否则,把它平移到顶点上也能成立,所以在顶点上是不劣的。
我们猜测最大的正三角形和图中差不多,另外两个点分别在第一个顶点的对边上。(例如下图)
存在一个 \(\triangle BFG\) 满足条件。
设 \(BF\) 重点为 \(E\) ,连接 \(GE\) 。
由三线合一,\(GE \perp BF\) ,又由角 \(ADG=90 \degree\) ,所以 \(FEDG\) 四点共圆。
所以 \(\angle FDG= \angle EFG= 60\degree\) ,同理 $\angle ECD=60 \degree $ 。
所以 \(\triangle DEC\) 是正三角形,即 $E $ 是定点。
可以通过找出矩形的 \(E\) 点,找到最大的三角形。
出现一个问题,就是在 \(E\) 要在正方形内。所以要判断一下。
怎么判断呢?做 \(EH \perp DC\) ,则有 \(EH=\frac{\sqrt{3}}{2} DC\) ,相当于只需要比较长和宽的比值即可。
如果 $E $ 不在正方形呢,那么找类似图二的三角形即为最大的。
即可。
Code:
#include<bits/stdc++.h>
using namespace std;
long double sq3=1.732050807568877;
int main(){
long double a,b;
cin>>a>>b;
if(a>b) swap(a,b);
if(a/sq3*2<b){
cout<<fixed<<setprecision(11)<<a*1.000/sq3*2;
return 0;
}
long double x=a/2.0000000;
long double y=a/2.0000000*sq3;
long double dlt=2*y-b;
long double ans=(b-dlt)*(b-dlt)+a*a;
ans=sqrt(ans);
cout<<fixed<<setprecision(11)<<ans;
return 0;
}
G - Count Strictly Increasing Sequences (Diff. 2,340)
简要题意
你有 \(n\) 个数,每个数长度为 \(m\)。
不过这 \(n\) 个数中,可能有某些位不确定,需要你在每个 ?
位置上 \(0\) 到 \(9\) 之间填一个数。设你填出来的序列是 \(\{S_i\}\)。
请你求出,在所有可能的填数方案中,有多少种满足 \(S_1 < S_2 < \dots < S_n\)?答案对 \(998244353\) 取模。你构造的 \(\{S_i\}\) 允许前导零存在。
数据范围:\(n,m \le 40\)。
输入格式
一行两个整数,\(n,m\) 。
接下来 \(n\) 行,,每行一个长度为 \(m\) 的字符串,表示第 \(i\) 个数。
输出格式
一行一个整数,表示方案个数,答案对 \(998,244,353\) 取模。
Sample:
Input:
3 2
?0
??
05
Output:
4
样例解释:可能的填数方案为:
\((00,01,05),(00,02,05),(00,03,05),(00,04,05)\) 。
Solution:
很直观的想法是数位 DP ,考虑每一位的填数性质按位做,但发现不太好转移。
考虑再加维度,(毕竟 \(n,m\) 很小)在状态定义中加入当前考虑的区间。
设计状态 \(dp[now][l][r][st]\) ,表示当前考虑数的第 \(i\) 位,当前位的值大于等于 \(st\) ,在考虑第 \(l\) 到第 \(r\) 个数。
显然目标状态为 \(dp[1][1][n][0]\) 。
考虑状态转移,在区间 \([l,r]\) 中,存在一个断点 \(k\) ,将区间分成两部分。对于 \([l,k]\) ,我们让它们当前考虑的这一位 \(now\) 的值相同,均为 \(st\) 。接下来,我们把排序目标转移到下一位继续 \(dp\);
对于 \([k+1,r]\) ,则它们当前位置已经比区间 \([l,k]\) 的数大了,相当于当前位 \(now\) 的值要大于等于 \(st+1\) ,继续 \(dp\) 即可。此时我们把区间割成了两部分,互不相干,满足区间 \(dp\) 的性质。
状态转移方程:
用记忆化搜索写,比较方便。
Code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=998244353;
char a[45][45];
int dp[45][45][45][15];
int n,m;
int dfs(int now,int l,int r,int st){
if(now>m){
return (l==r);
}
if(l>r){
return 1;
}
if(st>9){
return 0;
}
if(dp[now][l][r][st]>=0) return dp[now][l][r][st];
int ans=dfs(now,l,r,st+1);
for(int i=l;i<=r;i++){
if(a[i][now]!='?'&&a[i][now]!=st+'0') break;
ans=(ans+dfs(now+1,l,i,0)*dfs(now,i+1,r,st+1)%mod)%mod;
}
dp[now][l][r][st]=ans;
return ans;
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++) cin>>a[i][j];
}
memset(dp,-1,sizeof dp);
cout<<dfs(1,1,n,0)<<endl;
return 0;
}
Ex - Rating Estimator(Diff. 2,248)
简要题意
给定一个人 \(n\) 场比赛的表现分 \(a_i\) ,在第 \(k\) 场比赛后 \((1 \le k \le n)\) ,他的 \(rating\) 变化为 \(\frac{1}{n} \sum\limits_{1 \le j \le k} a_j\) 。
但是当一个人的 \(rating\) 达到 \(B\) 的时候,它的 \(rating\) 就不再变化了。
给定 \(q\) 个询问,每次询问给定两个数 \(a_i\) 和 \(x_i\) ,表示这个人第 \(a_i\) 场比赛的表现分变成 \(x_i\) ,求修改后他最终的 \(rating\) ,操作可持久化。
\(n \le 5\times 10^5,q \le 10^5\) 。
Sample:
Input:
5 6 7
5 1 9 3 8
4 9
2 10
1 0
3 0
3 30
5 100
1 100
Output:
6.000000000000000
7.500000000000000
6.333333333333333
5.400000000000000
13.333333333333334
13.333333333333334
100.000000000000000
Solution:
先推一下式子,考虑到什么时候 \(rating\) 才爆分。
考虑构造一个数组 \(\{b_n\}\) ,满足 \(b_i=a_i-B(1\le i \le n)\) ,那么当 \(b_i\) 的前缀和第一次大于 \(0\) 时完成爆分。
直接用线段树维护区间里前缀和的最大值。
考虑查询的时候要线段树上二分找到第一次爆分,所以还要维护区间和。
线段树上二分的一些细节见代码。
Code:
#include<bits/stdc++.h>
#define int long long
using namespace std;
struct tree{
int id,l,r,mx,sum,lazy;
}t[2000010];
int a[500010];
void pushup(int id){
t[id].sum=t[id<<1].sum+t[id<<1|1].sum;
t[id].mx=max(t[id<<1].mx,t[id<<1].sum+t[id<<1|1].mx);
}
void pushdown(int id){
if(t[id].lazy!=0){
t[id<<1].lazy=t[id<<1|1].lazy=t[id].lazy;
t[id<<1].sum=t[id].lazy*(t[id<<1].r-t[id<<1].l+1);
t[id<<1|1].sum=t[id].lazy*(t[id<<1|1].r-t[id<<1|1].l+1);
t[id].lazy=0;
}
}
void build(int id,int l,int r){
t[id].l=l;
t[id].r=r;
t[id].sum=t[id].lazy=t[id].mx=0;
if(l==r){
t[id].sum=a[l];
t[id].mx=a[l];
return;
}
int mid=(l+r)>>1;
build(id<<1,l,mid);
build(id<<1|1,mid+1,r);
pushup(id);
}
void update(int id,int l,int r,int v){
if(l<=t[id].l&&t[id].r<=r){
t[id].sum=v*(t[id].r-t[id].l+1);
t[id].lazy=v;
t[id].mx=v*(t[id].r-t[id].l+1);
return;
}
pushdown(id);
int mid=(t[id].l+t[id].r)>>1;
if(l<=mid) update(id<<1,l,r,v);
if(r>mid) update(id<<1|1,l,r,v);
pushup(id);
}
pair<int,int> query(int id,int l,int r,int sum){
// cout<<id<<" "<<l<<" "<<r<<" "<<sum<<" "<<t[id].sum<<" "<<t[id<<1].mx<<endl;
if(t[id].l==t[id].r){
return {t[id].l,sum+t[id].mx};
}
pushdown(id);
if(sum+t[id<<1].mx>=0){
return query(id<<1,l,r,sum);
}else{
return query(id<<1|1,l,r,sum+t[id<<1].sum);
}
}
signed main(){
int n,b,q;
cin>>n>>b>>q;
for(int i=1;i<=n;i++){
cin>>a[i];
a[i]-=b;
}
build(1,1,n);
while(q--){
int op,x;
cin>>op>>x;
x-=b;
update(1,op,op,x);
// return 0;
pair<int,int> pr=query(1,1,n,0);
// cout<<pr.first<<" "<<pr.second<<endl;
double ans=b+1.000000*pr.second/pr.first;
cout<<fixed<<setprecision(10)<<ans<<endl;
}
}