jz集训 8.9

Day 9

125

T1 走格子

Solition

伪装成搜索的最短路233
读题看出来走的方式有两种:普通移动和传送门移动。
普通移动就枚举每个点,朝4个方向建边。
麻烦的是传送门移动。
我们注意到每个点上下左右都一定会有一个传送门,我们可以对他们之间的任意一个建一个传送门。
但是要使用这些个传送门是有代价的,我们需要普通移动到最近的墙放个门,移动到想要去的门那里。
预处理出每个’.‘点到最近的’#'的距离,那么一个点到一个传送门的代价就是它到最近的墙的距离。bfs预处理即可。

#include <cstdio>
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
typedef pair<int, int> p;
const int N = 510;
const int INF = 0x3f3f3f3f;
const int dx[4]={-1, 1, 0, 0};
const int dy[4]={0, 0, -1, 1};
char read(){
	char ch=getchar();
	while(ch!='.' && ch!='#' && ch!='C' && ch!='F'){
		ch=getchar();
	}
	return ch;
}
int n,m,sx,sy,ex,ey;
struct edge{
	int u,to,nxt,w;
}e[(N*N)<<4];
char a[N][N];
int cnte,head[N*N],dis[N*N];
bool vis[N*N];
int d[N][N],cntw;//d[i][j]是(i, j)到最近的墙的距离
p wall[N*N];//是墙的点
void add(int u,int v,int w){
	e[++cnte].to=v;
	e[cnte].nxt=head[u];
	e[cnte].w=w;
	e[cnte].u=u;
	head[u]=cnte;
}
int id(int x,int y){
	return m*(x-1)+y;
}
void bfs(){
	queue<p> q;
	for(int i=1; i<=cntw; i++){
		q.push(wall[i]);
	}
	while(!q.empty()){
		p u=q.front();
		q.pop();
		int ux=u.first;
		int uy=u.second;
		for(int i=0; i<4; i++){
			int nx=ux+dx[i];
			int ny=uy+dy[i];
			if(nx>=1 && nx<=n && ny>=1 && ny<=m && !d[nx][ny] && a[nx][ny]!='#'){
				d[nx][ny]=d[ux][uy]+1;
				q.push(make_pair(nx, ny));
			}
		}
	}
}
void spfa(){
	memset(dis, 0x3f, sizeof dis);
	queue<int> q;
	q.push(id(sx, sy));
	vis[id(sx, sy)]=1;
	dis[id(sx, sy)]=0;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=head[u]; i; i=e[i].nxt){
			int v=e[i].to;
			int w=e[i].w;
			if(dis[v]>dis[u]+w){
				dis[v]=dis[u]+w;
				if(!vis[v]){
					q.push(v);
					vis[v]=1;
				}
			}
		}
	}
}
int main(){
	freopen("cell.in","r",stdin);
	freopen("cell.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++){
		for(int j=1; j<=m; j++){
			a[i][j]=read();
			if(a[i][j]=='C'){
				sx=i;
				sy=j;
			}
			else if(a[i][j]=='F'){
				ex=i;
				ey=j;
			}
			else if(a[i][j]=='#'){
				wall[++cntw]=make_pair(i, j);
			}
		}
	}
	bfs();
	for(int i=2; i<n; i++){
		for(int j=2; j<m; j++){
			if(a[i][j]=='#'){
				continue;
			}
			for(int k=0; k<4; k++){
				int nx=i+dx[k];
				int ny=j+dy[k];
				if(nx>=1 && nx<=n && ny>=1 && ny<=m && a[nx][ny]!='#'){
					add(id(i, j), id(nx, ny), 1);
				}
			}
		}
	}
	for(int i=2; i<n; i++){
		for(int j=2; j<m; j++){
			if(a[i][j]=='#'){
				continue;
			}
			for(int k=0; k<4; k++){
				int nx=i+dx[k];
				int ny=j+dy[k];
				while(nx>=1 && nx<=n && ny>=1 && ny<=m && a[nx][ny]!='#'){
					nx+=dx[k];
					ny+=dy[k];
				}
				nx-=dx[k];
				ny-=dy[k];
				add(id(i, j), id(nx, ny), d[i][j]);
			}
		}
	}
	spfa();
	if(dis[id(ex, ey)]==INF){
		printf("no\n");
	}
	else{
		printf("%d\n",dis[id(ex, ey)]);
	}
	return 0;
}

T2 扭动的树

AFK

T3 旋转子段

Solution

1.如果一个序列[l,r]能成为最终答案,那么l,r两点对答案一定有贡献,不然我们可以取[l+1,r-1]作为答案。
2.我们记数字i的位置为a[i]。
如果数字i与数字j经过旋转能到位,一定有a[i]+i=a[j]+j,且a[i]+i为中心点的坐标(记1左边为1,1为2,1和2中间为3,2为4…)。
对于每个数,计算出a[i]+i,将 i push进入数组v[i+a[i]]。再进行根据该点到中心的距离sort一遍,枚举端点,记录贡献,还要加上不旋转区间本来就在自己位置上的点的贡献。用前缀和统计即可。

#include <cstdio>
#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
#include <ctime>
using namespace std;
const int N = 100010;
int n,a[N],s[N],cen,ans;
vector <int> v[N<<1];
bool cmp(int x,int y){
	return abs((x<<1)-cen)<abs((y<<1)-cen);
}
int main(){
	freopen("rotate.in","r",stdin);
	freopen("rotate.out","w",stdout);
	scanf("%d",&n);
	for(int i=1; i<=n; i++){
		scanf("%d",&a[i]);
		s[i]=s[i-1]+(a[i]==i);
		v[a[i]+i].push_back(i);
	}
	for(cen=1; cen<=(n<<1); cen++){
		sort(v[cen].begin(), v[cen].end(), cmp);
		int siz=v[cen].size();
		for(int i=0; i<siz; i++){
			int tmp1=v[cen][i];
			int tmp2=cen-v[cen][i];
			int L=min(tmp1, tmp2);
			int R=max(tmp1, tmp2);
			ans=max(ans, i+1+s[L-1]+(s[n]-s[R]));
		}
	}
	printf("%d\n",ans);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值