CSP20230917-4 阴阳龙 题解
〇、题面
题意过于混乱。
简单来说就是每次龙会出现在一个位置,从这个位置往八个方向走能走到一些员工。它到这些员工的距离就是它们 \(x\) 坐标差和 \(y\) 坐标差的最大值。
如果到最近的员工的距离大于到最近的边界的距离就摆烂,否则就把所有距离等于这个最近员工距离的员工全都旋转 \(t\) 次。
一次旋转可以理解为从一个方向逆时针移动到相邻方向,距离不变。
一、思路
题目中也说到了,每个位置最多一个员工。
因此每次旋转的员工不超过 \(8\) 个,直接旋转即可。
接下来重点在于找到最近的那些员工和处理它们的旋转。因为地图非常大而且操作次数多,所以需要最多为 \(\log\)。
直接记录每一条正反斜线和横线、竖线上的所有点,一次操作的时候直接 lower_bound 到这个方向上离龙最近的两个点。
之后按照题意简单模拟去掉距离超过最小值的点,并且直接移动它们。
移动就先把原本位置 erase 掉,再把新位置 insert 进去。
二、代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
#define fi first
#define se second
#define endl '\n'
#define mp make_pair
#define pb push_back
#define all(x) x.begin(),x.end()
#define Cl(x) memset(x,0,sizeof(x))
const bool DC=0;
const ll mod=0;
const int N=0;
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
ll qpow(ll a,ll b,ll p=mod){ll ans=1;for(;b;b>>=1,a=a*a%p)if(b&1)ans=ans*a%p;return ans;}
void NO(){cout<<"NO\n";}
void YES(){cout<<"YES\n";}
mt19937 rnd((unsigned long long)new char);
int n,m,p,q;
struct pos{
int x,y,id;
pos(int a=0,int b=0,int c=0){x=a,y=b,id=c;}
bool operator==(const pos&b)const{return x==b.x&&y==b.y;}
bool operator<(const pos&b)const{return x==b.x?y==b.y?id<b.id:y<b.y:x<b.x;}
};
ll ans;
pos a[100005];
int dir[8][2]={{-1,0},{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1}};
unordered_map<int,set<pos>>D1,D2,D3,D4;
/*
1 2 3
\|/
0-.-4
/|\
7 6 5
D1:x+y
D2:x
D3:x-y
D4:y
*/
void add(pos nw){// 添加
ans^=(1ll*nw.id*nw.x+nw.y);
int x=nw.x,y=nw.y;
D1[x+y].insert(nw),D2[x].insert(nw),D3[x-y].insert(nw),D4[y].insert(nw);
}
void del(pos nw){// 删除
ans^=(1ll*nw.id*nw.x+nw.y);
int x=nw.x,y=nw.y;
D1[x+y].erase(nw),D2[x].erase(nw),D3[x-y].erase(nw),D4[y].erase(nw);
}
vector<pos>v;
int x,y,t;
void chs(set<pos>s,int d){// 选择两个点,注意要特判掉位置相等的情况
if(s.empty())return;
auto it=s.lower_bound({x,y,0}),ti=it;
if(it!=s.end()){
if((*it)==pos(x,y,0)){if(++it!=s.end()) v.push_back(*it);}
else v.push_back(*it);
}
it=ti;
if(it!=s.begin())v.push_back(*(--it));
}
bool cmp(pos a,pos b){return max(abs(a.x-x),abs(a.y-y))<max(abs(b.x-x),abs(b.y-y));}
int getd(pos p){return p.x+p.y==x+y?p.x<x?1:5:p.x-p.y==x-y?p<x?7:3:p.x==x?p.y<y?6:2:p.y==y?p.x<x?0:4:-1;}// 找出这个点对应的方向
void __INIT__(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);}
void __SOLVE__(int _case){
cin>>n>>m>>p>>q;
for(int i=1;i<=p;i++)cin>>a[i].x>>a[i].y,a[i].id=i,add(a[i]);
while(q--){
cin>>x>>y>>t;
v.clear();
chs(D1[x+y],1);// 在四个方向找点
chs(D2[x],2);
chs(D3[x-y],3);
chs(D4[y],4);
if(v.empty())continue;
sort(v.begin(),v.end(),cmp);
int dmin=min({x-1,n-x,y-1,m-y}),d0=max(abs(v[0].x-x),abs(v[0].y-y));
if(dmin<d0)continue;
while(max(abs(v.back().x-x),abs(v.back().y-y))>d0)v.pop_back();// 去掉多余点
for(auto i:v)del(i);
for(auto i:v){
int d=(getd(i)+8-t%8)%8;// 这里我的 d 是顺时针标号,所以要 -t
add({x+d0*dir[d][0],y+d0*dir[d][1],i.id});
}
}
cout<<ans<<endl;
}
int main(){/*freopen(".in","r",stdin);freopen(".out","w",stdout);*/__INIT__();int T;DC?cin>>T,1:T=1;for(int _CASE=1;_CASE<=T;_CASE++)__SOLVE__(_CASE);return 0;}
三、总结
思维难度不大,主要是实现细节比较麻烦。
(样例太弱啦!!!