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;}

三、总结

思维难度不大,主要是实现细节比较麻烦。

(样例太弱啦!!!

posted @ 2023-10-24 10:50  cyx001  阅读(295)  评论(0编辑  收藏  举报