[题解]ABC351 D~F
D - Grid and Magnet
终于找到为什么WA 4个点了。今天回来又交了几遍发现WA的数量不稳定,除了4个还有5个。所以猜测是没有初始化的问题,一番检查发现是存地图的\(s\)数组,第\(0\)行没有初始化。之前做题没有遇到过特别访问第\(0\)行的,所以写代码时就粗枝大叶、一笔略过了。血的教训啊……要不然也能赛时轻松A掉这一题。
我们使用dfs跑连通块。但是有一个问题,多个连通块可能有公共部分,所以我们每跑完一次dfs似乎就要memset一下\(vis\)数组?但是这样显然会超时。
经过观察,这些重合的公共部分,都是磁铁周围的“磁场区域”。所以我们每跑一次连通块,遇到“磁场区域”就加入一个\(vector\),最后对\(vector\)中存储的所有经过的“磁场区域”单独把vis置为\(0\)即可。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m;
string s[1010];
bool vis[1010][1010];
int dx[4]{-1,0,1,0},dy[4]{0,1,0,-1};
vector<int> xx,yy;
int dfs(int x,int y){
if(x<1||x>n||y<1||y>m||vis[x][y]||s[x][y]=='#') return 0;
vis[x][y]=1;
if(s[x-1][y]=='#'||s[x+1][y]=='#'||s[x][y-1]=='#'||s[x][y+1]=='#'){
xx.push_back(x),yy.push_back(y);
return 1;
}
int ans=1;
for(int i=0;i<4;i++){
ans+=dfs(x+dx[i],y+dy[i]);
}
return ans;
}
signed main(){
cin>>n>>m;
for(int i=1;i<=m;i++) s[0]+=' ';
for(int i=1;i<=n;i++){
cin>>s[i];
s[i]=' '+s[i];
}
int ans=INT_MIN;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
ans=max(ans,dfs(i,j));
int len=xx.size();
for(int k=0;k<len;k++){
vis[xx[k]][yy[k]]=0;
}
xx.clear(),yy.clear();
}
}
cout<<ans;
return 0;
}
E - Jump Distance Sum
一开始想到的思路很复杂,先把\(n\)个点按照\(x+y\ mod\ 2\)分成\(2\)组,对于每一组用线段树维护……总之很繁多,虽然有完整的思路,理论上也应该可行,但是实现太麻烦就看题解了。
题目描述的距离叫切比雪夫距离,也就是\(x\)坐标之差和\(y\)坐标之差的最大值。
因为要求最大值,所以难以优化。但我们可以把切比雪夫坐标系转化成曼哈顿坐标系。这样切比雪夫距离就变成曼哈顿距离(\(x\)坐标之差\(+y\)坐标之差)了。转化方式是\((x,y)\to (\frac{x+y}{2},\frac{x-y}{2})\)。
可以借助上图理解一下,黑正方形上的点到原点曼哈顿距离为\(4\),蓝正方形上的点到原点切比雪夫距离为\(4\)。可以发现转化前橙色和灰蓝色点的切比雪夫距离就是转化后的曼哈顿距离。
具体证明见OI wiki。
注意:代码实现中因为可能得出浮点数所以省去了\(\div 2\),结果再\(\div 2\),效果相同。
同样按上面的方法把\(n\)个点分成\(2\)组,对于每一组进行转化。对于每一组,设有\(k\)个点,那么我们要求\(k\)个点两两之间曼哈顿距离。曼哈顿距离是横纵坐标的之,所以我们横、纵单独映射到数轴上考虑,再把两者结果相加即可。
我们接下来需要解决:数轴上\(k\)个点,两两之间的距离之和是多少?
这可以用前缀和来解决。首先对\(k\)个点进行排序。接下来,从第\(1\)个点开始遍历,设该点为\(x\),前面遍历过的点数为\(cnt\),遍历过的点坐标之和为\(sum\),那么加入当前点,距离和的增量显然就是\(cnt*x-sum\)。
把横纵坐标的答案相加就是这一组的答案;把两组答案相加就是最终答案。所以此处定义\(d[4]\),分别存储两组的\(x,y\)。对于\(d[0\sim 3]\),按上面的方法算出答案,求和即可。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
vector<int> d[4];
signed main(){
cin>>n;
for(int i=1;i<=n;i++){
int x,y;
cin>>x>>y;
int tx=(x+y),ty=(x-y);
if((x+y)%2==1){
d[0].push_back(tx),d[1].push_back(ty);
}else{
d[2].push_back(tx),d[3].push_back(ty);
}
}
int ans=0;
for(int i=0;i<4;i++){
sort(d[i].begin(),d[i].end());
int sum=0,len=d[i].size();
for(int j=0;j<len;j++){
ans+=j*d[i][j]-sum;
sum+=d[i][j];
}
}
cout<<ans/2;
return 0;
}
F - Double Sum
后悔赛时没看这道题,感觉比E简单不知道多少。
开一个线段树,每个位置\(x\)存两个值\(\{cnt,sum\}\),分别表示\(x\)这个值出现了\(cnt\)次,\(sum=cnt*x\)。由于用的是类似桶的存储方式,所以需要先对\(A\)进行离散化存入\(B\)中。
正序遍历\(B\),对于每个元素,先查询比自己小的元素总数\(cnt\)和总和\(sum\)。那么加入当前元素的贡献就是\(cnt*A_i-sum\),累加进\(ans\)里,并将\(B_i\)位置\(cnt+=1,sum+=A_i\)。
最终输出\(ans\)即可。
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define lc (x<<1)
#define rc (x<<1|1)
#define MAXN 400010
using namespace std;
int n,a[MAXN],temp[MAXN],b[MAXN];
pair<int,int> sum[4*MAXN];
const pair<int,int> operator+(const pair<int,int>& p1,const pair<int,int>& p2){
pair<int,int> ans;
ans.first=p1.first+p2.first;
ans.second=p1.second+p2.second;
return ans;
}
void build(int l,int r,int x){
if(l==r) return;
int mid=(l+r)>>1;
build(l,mid,lc);
build(mid+1,r,rc);
sum[x]=sum[lc]+sum[rc];
}
void change(int a,pair<int,int> v,int l,int r,int x){
if(l==r){
sum[x]=sum[x]+v;
return;
}
int mid=(l+r)>>1;
if(a<=mid) change(a,v,l,mid,lc);
else change(a,v,mid+1,r,rc);
sum[x]=sum[lc]+sum[rc];
}
pair<int,int> query(int a,int b,int l,int r,int x){
if(a>b) return {0,0};
if(a<=l&&r<=b) return sum[x];
int mid=(l+r)>>1;
pair<int,int> ans{0,0};
if(a<=mid) ans=ans+query(a,b,l,mid,lc);
if(b>mid) ans=ans+query(a,b,mid+1,r,rc);
return ans;
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
temp[i]=a[i];
}
sort(temp+1,temp+n+1);
int len=unique(temp+1,temp+n+1)-temp-1;
for(int i=1;i<=n;i++) b[i]=lower_bound(temp+1,temp+len+1,a[i])-temp;
build(1,n,1);
int ans=0;
for(int i=1;i<=n;i++){
auto t=query(1,b[i]-1,1,n,1);
ans+=t.first*a[i]-t.second;
change(b[i],{1,a[i]},1,n,1);
}
cout<<ans;
return 0;
}