[题解]AtCoder Beginner Contest 408(ABC408) A~G
A - Timeout
根据题意,若令\(T[0]=0\),则:
- 答案为
Yes\(\iff\)对于\(i\in [1,n]\),都有\(T[i]-T[i-1]\le S\)。
时间复杂度\(O(n)\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,s,last;
signed main(){
cin>>n>>s;
for(int i=1,x;i<=n;i++){
cin>>x;
if(x-last>s) cout<<"No\n",exit(0);
last=x;
}
cout<<"Yes\n";
return 0;
}
B - Compression
先排序,再使用unique函数进行去重即可。时间复杂度\(O(n\log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define N 110
using namespace std;
int n,a[N];
signed main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+1+n);
int len=unique(a,a+1+n)-a-1;
cout<<len<<"\n";
for(int i=1;i<=len;i++) cout<<a[i]<<" ";
return 0;
}
也可以使用set等数据结构来实现。时间复杂度是\(O(n\log n)\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n;
set<int> s;
signed main(){
cin>>n;
for(int i=1,x;i<=n;i++) cin>>x,s.insert(x);
cout<<s.size()<<"\n";
for(int i:s) cout<<i<<" ";
return 0;
}
C - Not All Covered
定义桶数组\(b\)。对于\(i\in [1,m]\),将\(b[L_i\sim R_i]\)每个位置\(+1\),需要前缀和优化。
最后答案即为\(\min\limits_{i=1}^n b[i]\)。
时间复杂度\(O(n+m)\)。
点击查看代码
#include<bits/stdc++.h>
#define N 1000010
using namespace std;
int n,m,a[N],ans=INT_MAX;
signed main(){
cin>>n>>m;
for(int i=1,l,r;i<=m;i++) cin>>l>>r,a[l]++,a[r+1]--;
for(int i=1;i<=n;i++){
a[i]+=a[i-1];
ans=min(ans,a[i]);
}
cout<<ans<<"\n";
return 0;
}
D - Flip to Gather
赛时思路
定义\(a[i]\)为\(s[1\sim i]\)中\(1\)的个数。
如果最终将\([l,r]\)区间置为\(1\)(由于区间可以为空,所以规定\(r\ge l-1\)),那么操作次数就是:
最终答案即为:
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define N 200010
using namespace std;
int t,n,a[N];
string s;
signed main(){
cin>>t;
while(t--){
cin>>n>>s;
s=' '+s;
for(int i=1;i<=n;i++) a[i]=a[i-1]+s[i]-'0';
int tmp=a[n],ans=LLONG_MAX;
for(int i=1;i<=n;i++) a[i]=2*a[i]-i;
for(int i=0,minn=LLONG_MAX;i<=n;i++){
minn=min(minn,a[i]);
ans=min(ans,minn-a[i]);
}
cout<<ans+tmp<<"\n";
}
return 0;
}
题解思路
本质上差不多。
定义\(a[i]\)为\(s[1\sim i]\)中\(0\)的个数,\(b[i]\)为\(s[1\sim i]\)中\(1\)的个数。
如果最终将\([l,r]\)区间置为\(1\),那操作次数就是:
令\(t[i]=b[i]-a[i]\),整理得:
统计起来就和上面同理了。
E - Minimum OR Path
为了让答案尽可能小,我们尽可能要让答案最高位为\(0\),其次要尽可能让答案次高位为\(0\)……于是考虑贪心。
定义集合\(S=\varnothing\),\(S\)中若存在\(i\),则表示“所经过的边,第\(i\)位必须为\(0\)”。
从最高位开始,依次考虑每一位\(k\):
- 如果在\(S\cup\{k\}\)的限制下,仍然能从\(1\)走到\(n\),则令\(S\leftarrow S\cup\{k\}\)。
- 否则,将答案累加\(2^k\)。
第一条的判定可以使用并查集或者DFS来实现。
时间复杂度分别是\(O((n+m)\alpha(n)\log V)\)和\(O((n+m)\log V)\)。
并查集
#include<bits/stdc++.h>
#define N 200010
#define M 200010
#define int long long
using namespace std;
int n,m,ans,fa[N];
struct edge{int u,v,w;}e[M];
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
void merge(int u,int v){u=find(u),v=find(v);if(u!=v) fa[u]=v;}
signed main(){
cin>>n>>m;
for(int i=1;i<=m;i++) cin>>e[i].u>>e[i].v>>e[i].w;
int s=0;
for(int i=29;~i;i--){
s^=(1<<i);
for(int j=1;j<=n;j++) fa[j]=j;
for(int j=1;j<=m;j++) if(!(e[j].w&s)) merge(e[j].u,e[j].v);
if(find(n)!=find(1)) s^=(1<<i),ans^=(1<<i);
}
cout<<ans<<"\n";
return 0;
}
DFS
#include<bits/stdc++.h>
#define N 200010
#define int long long
using namespace std;
int n,m,ans;
struct edge{int to,w;};
vector<edge> G[N];
bitset<N> vis;
void add(int u,int v,int w){G[u].emplace_back(edge{v,w});}
void dfs(int u,int x){
vis[u]=1;
for(auto i:G[u]) if(!vis[i.to]&&!(i.w&x)) dfs(i.to,x);
}
signed main(){
cin>>n>>m;
for(int i=1,u,v,w;i<=m;i++){
cin>>u>>v>>w;
add(u,v,w),add(v,u,w);
}
int s=0;
for(int i=29;~i;i--){
vis=0,dfs(1,s|(1<<i));
if(vis[n]) s|=(1<<i);
else ans|=(1<<i);
}
cout<<ans<<"\n";
return 0;
}
F - Athletic
下文的\(a\)表示高度数组。
考虑DP。令\(f[i]\)表示首次移动到\(i\)的最大可能移动次数。
那么从高到低遍历每个脚手架\(i\),有转移:
如果没有\(a_j\ge a_i+D\)的限制,直接用线段树来维护区间\(\max\)即可。
现在加上了这条限制,我们仅需在遍历到高度\(i\)时,才将高度为\(i+D\)的\(f\)值加入线段树,加入之前初始值都是\(-1\)。这样就满足了线段树里的脚手架高度都\(\ge i+D\)的条件,并且未加入的脚手架不会产生贡献。
时间复杂度\(O(n\log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define N 500010
#define int long long
#define lc (x<<1)
#define rc (x<<1|1)
using namespace std;
int n,d,r,a[N],pos[N],f[N];
struct SEG{
int maxx[N<<2];
void upd(int x){maxx[x]=max(maxx[lc],maxx[rc]);}
void sep(int x,int a,int v,int l,int r){
if(l==r) return maxx[x]=v,void();
int mid=(l+r)>>1;
if(a<=mid) sep(lc,a,v,l,mid);
else sep(rc,a,v,mid+1,r);
upd(x);
}
int query(int x,int a,int b,int l,int r){
if(a<=l&&r<=b) return maxx[x];
int mid=(l+r)>>1,ans=-1e9;
if(a<=mid) ans=max(ans,query(lc,a,b,l,mid));
if(b>mid) ans=max(ans,query(rc,a,b,mid+1,r));
return ans;
}
}tr;
signed main(){
memset(tr.maxx,-1,sizeof tr.maxx);
cin>>n>>d>>r;
for(int i=1;i<=n;i++) cin>>a[i],pos[a[i]]=i;
for(int i=n;i;i--){//高度
if(i+d<=n) tr.sep(1,pos[i+d],f[i+d],1,n);
f[i]=1+tr.query(1,max(1ll,pos[i]-r),min(n,pos[i]+r),1,n);
}
cout<<*max_element(f+1,f+1+n)<<"\n";
return 0;
}
G - A/B < p/q < C/D
定义\(f(\frac{A}{B},\frac{C}{D})\)为\((P,Q)\)使得:
- \(\frac{A}{B}<\frac{P}{Q}<\frac{C}{D}\)。
- \(Q\)尽可能小。
考虑如何计算\(f(\frac{A}{B},\frac{C}{D})\)。
-
如果\(\frac{A}{B}<1\)且\(\frac{C}{D}>1\),答案就是\((1,1)\)。
-
否则将\(\frac{A}{B}\)和\(\frac{C}{D}\)同时取倒数得到\(\frac{B}{A}\)和\(\frac{D}{C}\),再同时减去\(\lfloor\frac{B}{A}\rfloor\)得到\(\frac{B'}{A}\)和\(\frac{D'}{C}\)。
计算\(f(\frac{B'}{A},\frac{D'}{C})\)得到\((p',q')\),按上面的步骤即可反解出原来的\((p,q)\)。
正确性证明下午写。
时间复杂度与gcd相同,是\(O(T\log V)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int t,a,b,c,d,p,q;
void gcd(int a,int b,int &p,int &q,int c,int d){
if(a<b&&c>d) p=1,q=1;
else{
gcd(d%c,c,q,p,b-(d/c)*a,a);
q+=(d/c)*p;
}
}
signed main(){
cin>>t;
while(t--){
cin>>a>>b>>c>>d;
gcd(a,b,p,q,c,d);
cout<<q<<"\n";
}
return 0;
}
浙公网安备 33010602011771号