AT_abc413
又是战犯的一周啊。
再见宣言真好听。
A.Content Too Large
translation:
判断是否有 \(\sum_{i=1}^n A_i\le M\)。
无脑题,不给代码了。赛时除了手速有点慢以外没啥别的问题。
B.cat 2
translation:
给定 \(n\) 个字符串 \(S_1,S_2,\dots,S_n\),任选两个不同的字符串前后连接可以得到 \(n(n-1)\) 个字符串。求这 \(n(n-1)\) 个字符串中有多少个不同的字符串。
\(n\le 100\),所以对照题面逐句实现即可,拿个 set 去下重就没有然后了。不给代码。
C.Large Queue
translation:
有一个队列,初始为空。有 \(q\) 次操作,如下:
1 c x
:在队列末尾插入 \(c\) 个 \(x\)。
2 k
:弹出队首的 \(k\) 个数,并输出他们的和。
逐句实现显然不现实。注意到对于某个数 \(x\),如果我们知道了它在需要弹出的数中出现的次数,那么可以 \(O(1)\) 求贡献。于是记录每个数插入后的结尾位置,每次相当于查询一段区间的和。可以通过 lower_bound
出边界处的两个元素来计算。中间的数暴力扫一遍,由于查询的区间是连续且不重合的,所以每个数最多会被暴力扫一遍,时间复杂度正确,为 \(O(n\log n)\)。
code
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,ed[200005],x[200005],fr=1,tot;
signed main(){
cin>>n;
while(n--){
int op;cin>>op;
if(op==1){
int c;tot++;
cin>>c>>x[tot];
ed[tot]=ed[tot-1]+c;
}
else{
int k;cin>>k;
int l=lower_bound(ed+1,ed+tot+1,fr)-ed;
int r=lower_bound(ed+1,ed+tot+1,fr+k-1)-ed;
int ans=(ed[l]-fr+1)*x[l];
for(int i=l+1;i<=r;i++)ans+=(ed[i]-ed[i-1])*x[i];
ans-=(ed[r]-(fr+k-1))*x[r];
cout<<ans<<'\n';
fr+=k;
}
}
return 0;
}
D.Make Geometric Sequence
translation:
给定一个数列,判断它是不是等比数列的重排。
首先我们注意到等比数列的绝对值单调,因此先按照绝对值给数列排序。
发现如果公比不为 \(-1\),那么排完序后必须有 \(a_{i-1}a_{i+1}=a_i^2(1<i<n)\),如果没有则不是等比数列,然后特判一下公比为 \(-1\) 的情况即可。
有一些细节,赛时这题吃了 4 发罚时,战犯了。
code
#include<bits/stdc++.h>
using namespace std;
#define int long long
int T,a[200005],n;
bool cmp(int x,int y){
return abs(x)<abs(y);
}
void solve(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
sort(a+1,a+n+1,cmp);
if(abs(a[1])==abs(a[n])){
int cnt=0;
for(int i=1;i<=n;i++)if(a[i]>0)cnt++;
if(cnt==n||cnt==0)cout<<"Yes\n";
else if(n%2==0&&cnt==n/2)cout<<"Yes\n";
else if(n%2==1&&(cnt==n/2||cnt==n-n/2))cout<<"Yes\n";
else cout<<"No\n";
return;
}
for(int i=2;i<n;i++){
if(a[i-1]*a[i+1]!=a[i]*a[i]){
cout<<"No"<<'\n';
return;
}
}
cout<<"Yes\n";
}
signed main(){
cin>>T;
while(T--)solve();
}
E.Reverse 2^i
translation:
给定一个长度为 \(2^n\) 的排列 \(P\),下标从 \(0\) 起,可以进行以下操作若干次:
选择 \(a,b\) 使得 \(0\le a\times 2^b<(a+1\times 2^b)\le 2^n\),并翻转 \(P_{a\times 2^b}\sim P{(a+1)\times 2^b-1}\) 这一段。
求经过任意次操作后,最小字典序的排列。
随便手玩一下可以发现,对于任意一段区间 \(P_{a\times 2^b}\sim P{(a+1)\times 2^b-1}\),我们总可以把这段区间的最小值放到区间的第一位。具体操作为,如果最小值在前半段则不动,最小值在后半段则翻转,然后递归到左右区间。
显然这是最优解,又因为在这个过程中每个数最多被翻转 \(n\) 次,因此总复杂度 \(O(n2^n)\) 随便过。
code
#include<bits/stdc++.h>
using namespace std;
int T,n,a[262150];
void solve(int l,int r){
if(l==r)return;
int ps=l,mid=(l+r)>>1;
for(int i=l+1;i<=r;i++)if(a[i]<a[ps])ps=i;
if(ps>mid){
for(int i=l;i<=mid;i++)swap(a[i],a[r-(i-l)]);
}
solve(l,mid);
solve(mid+1,r);
}
signed main(){
cin>>T;
while(T--){
cin>>n;
for(int i=1;i<=(1<<n);i++)cin>>a[i];
solve(1,1<<n);
for(int i=1;i<=(1<<n);i++)cout<<a[i]<<" ";
cout<<'\n';
}
}
F.No Passage
translation:
给定一个 \(n\times m\) 的网格,其中有一些方格是目标格。现在 A 和 B 在这个网格上玩游戏:
每次 B 从四方向中选择一个方向封禁,A 从剩下三个方向中选择一个方向移动。A 的目标是到达一个目标格,如果可以则最小化移动步数。B 的目标是阻止 A 到达目标格。如果 A 一定可以到达,则 B 会最大化移动步数。
在此情况下,求使 A 可以到达目标格的所有起始点所需移动步数之和。
发现如果一个格子相邻的四格中至少有两个可以到达目标格,那么这个格子也可以到达目标格,所需步数为周边可到达目标格的格子中步数次大值 \(+1\)。所有目标格都可以到达目标格,且步数为 0。然后每次像 BFS 一样更新相邻格,如果当前格更新后邻格可能可以更新,则将邻格加入队列。全部更新一遍之后直接求和即可。
code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define F first
#define S second
const int dx[4]={1,-1,0,0},dy[4]={0,0,1,-1};
int n,m,k;
int a[3005][3005];
int du[3005][3005];
queue<pair<int,int> >q;
signed main(){
cin>>n>>m>>k;
memset(a,-1,sizeof(a));
for(int i=1;i<=k;i++){
int x,y;cin>>x>>y;
a[x][y]=0;
for(int j=0;j<4;j++){
int nx=x+dx[j],ny=y+dy[j];
if(nx<1||ny<1||nx>n||ny>m)continue;
q.push({nx,ny});
}
}
while(q.size()){
int x=q.front().F,y=q.front().S;
q.pop();
// if(a[x][y]!=-1)continue;
int cnt=0;
int m1=1e9,m2=1e9;
for(int j=0;j<4;j++){
int nx=x+dx[j],ny=y+dy[j];
if(nx<1||ny<1||nx>n||ny>m)continue;
if(a[nx][ny]!=-1){
cnt++;
if(a[nx][ny]<m1){
m2=m1;
m1=a[nx][ny];
}
else if(a[nx][ny]<m2)m2=a[nx][ny];
}
}
if(cnt<=1||(a[x][y]!=-1&&m2+1>=a[x][y]))continue;
a[x][y]=m2+1;
for(int j=0;j<4;j++){
int nx=x+dx[j],ny=y+dy[j];
if(nx<1||ny<1||nx>n||ny>m)continue;
if(a[nx][ny]!=-1&&a[nx][ny]<=m2+2)continue;
q.push({nx,ny});
}
}
int ans=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(a[i][j]!=-1)ans+=a[i][j];
}
}
cout<<ans<<endl;
return 0;
}
G.Big Banned Grid
translation:
给定一个 \(n\times m\) 的网格,其中有一些方格有障碍,求 \((1,1)\) 与 \((n,m)\) 是否四方向连通。
\(n,m\le 2\times 10^5\),直接跑搜索显然不现实。所以应用类似平面图转对偶图的思想,障碍与障碍八方向连边,然后跑一次连通块并查集。检查上下边界,左右边界,左上边界,右下边界是否有连通。如果有那么 \((1,1)\) 和 \((n,m)\) 不连通,否则连通。
code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define F first
#define S second
const int dx[8]={1,-1,0,0,1,1,-1,-1},dy[8]={0,0,1,-1,1,-1,1,-1};
map<pair<int,int>,int>mp;
queue<int>q;
vector<int>G[200010];
int n,m,k,fa[200010];
int find(int x){
if(fa[x]==x)return x;
return fa[x]=find(fa[x]);
}
void merge(int x,int y){
x=find(x),y=find(y);
if(x==y)return;
fa[x]=y;
}
signed main(){
cin>>n>>m>>k;
for(int i=1;i<=k+5;i++)fa[i]=i;
for(int i=1;i<=k;i++){
int x,y;cin>>x>>y;
mp[{x,y}]=i;
for(int j=0;j<8;j++){
int nx=x+dx[j],ny=y+dy[j];
if(nx<1||ny<1||nx>n||ny>m)continue;
if(mp[{nx,ny}])merge(i,mp[{nx,ny}]);
}
if(x==1)merge(i,k+1);
if(y==1)merge(i,k+2);
if(x==n)merge(i,k+3);
if(y==m)merge(i,k+4);
}
int f1=find(k+1),f2=find(k+2),f3=find(k+3),f4=find(k+4);
if(f1==f2||f1==f3||f2==f4||f3==f4)cout<<"No\n";
else cout<<"Yes\n";
return 0;
}