CSYZDay2模拟题解

T1.rotate

【问题描述】

ZYLN张牌编号分别为1, 2……N。他把这N张牌打乱排成一排,然后他要做一次旋转使得旋转后固定点尽可能多。如果第i个位置的牌的编号为i,我们就称之为固定点。旋转可以被认为是将其中的一个子段旋转180度,这意味着子段的第一张牌和最后一张牌交换位置,以及第二张牌和倒数第二张牌交换位置,等等。写一个程序,找到旋转子段(子段长度可以为1)。

 

【输入】

第一行包含一个整数 N (1 ≤ N ≤100 000)

第二行有N个数,第i个数表示旋转之前第i个位置的牌的编号。

输出】

找到固定点最多的旋转所选的子段,输出旋转之后固定点的个数。

 

【输入输出样例】

样例1

样例2

rotate.in

rotate.out

rotate.in

rotate.out

4

3 2 1 4

4

2

1 2

2

样例解释:

在样例1中,只需要旋转的子段[3,2,1],将排列变成1 2 3 4,旋转后所有的牌都为固定点。答案为4

在样例2中,所有的牌已经在固定点,旋转子段[1]或者子段[2],答案为2

 

【数据范围】

    30%的数据满足:N ≤ 500

  60%的数据满足:N ≤ 5000

100%的数据满足:1 ≤ N ≤ 100 000

注意本题题目描述有误,最后的数据应该是5*10^5。

这个题怎么做呢……考试的时候只想出来n^3暴力结果还特别智障的写错了参数导致爆零。

30分就是直接枚举所有区间之后直接判断。60分的发现也很好想,就是每次枚举旋转区间的中心点,之后再枚举旋转的长度(注意这里是从短到长,就像莫队一样往两边扫统计答案,这样才是n^2的复杂度

至于100分的做法,我们首先假设现在最优的反转区间是[l,r],那么如果有a[l] != r &&  a[r] != l,那么也就相当于反转这一次区间对于这个区间两个端点是没有任何作用的,也就是说这两个端点对答案没有任何贡献,所以直接删去即可,那么[l+1,r-1]也是一个最优解。依次类推,直到出现一个位置使得a[l] == r || a[r] == l.

 

那么也就是说,一个区间如果想成为最优解,那么必须满足左右端点之中最少有一个有反转过后成为不动点。也就是答案必定在所有的[min(a[i],i),max[(a[i],i)]之中产生,其中i取遍1~n。那么我们就变成了一个查询问题,我们只需要查询n次即可获得结果,问题在于怎么在N次查询之中快速获取反转区间后的不动点个数。我们知道,对于一个反转区间,如果在反转过后其内部有点成为不动点,那么在反转之前,这个点必然满足a[j] + j == a[i] + i.,并且这个点对也可能有成为最优解的反转区间。所以我们把所有的旋转区间按照中心旋转点分类(每个用一个vector存起来),之后每次按照反转区间长度排序(我们要从小的开始取,之后再取大的),然后每次判断的时候我们发现这个点再vector中的位置,就是它内部所包含的反转过后能成为不动点的点的数目(因为比他小的都包含在里面,随着它反转必然成为不动点),然后还要加上一(这个点本身的贡献)。这样的话,剩下的不反转的区间直接使用前缀和维护不动点个数即可。总复杂度O(nlogn)。

 

看一下代码。

 

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<cmath>
#include<queue>
#include<vector>
#include<set>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar('\n')

using namespace std;
typedef long long ll;
const int M = 500005;

int read()
{
    int ans = 0,op = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-') op = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        ans *= 10;
        ans += ch - '0';
        ch = getchar();
    }
    return ans * op; 
}

vector <int> g[M<<1];
int n,a[M],sum[M],cur,maxn = -1;

bool cmp(int x,int y)//按反转的长度排序
{
    return abs((x<<1)-cur) < abs((y<<1)-cur);
}

int main()
{
//    freopen("a.in","r",stdin);
//    freopen("rotate1.out","w",stdout);
    n = read();
    rep(i,1,n) 
    {
        a[i] = read();
        g[a[i]+i].push_back(i);//压入vector
        if(a[i] == i) sum[i] = sum[i-1] + 1;
        else sum[i] = sum[i-1];//前缀和
    }
    rep(i,2,n<<1)//枚举反转中心
    if(!g[i].empty())
    {
        cur = i;
        sort(g[i].begin(),g[i].end(),cmp);//排序
        int k = g[i].size();
        rep(j,0,k-1)
        {
            int l = g[i][j];
            int r = i - g[i][j];
            if(l > r) swap(l,r);//找到区间左右端点
            maxn = max(maxn,sum[l-1] + sum[n] - sum[r] + j + 1);//更新答案(原理如上文所述)
        }
    }
    printf("%d\n",maxn);
    return 0;
}

T2.cell

【问题描述】

CYJ想找到他的小伙伴FPJ.CYJFPJ现在位于一个房间里,这个房间的布置可以看成一个NM列的矩阵,矩阵内的每一个元素会是下列情况中的一种:

  1. 障碍区域这里有一堵墙(‘#’表示).
  2. 这是CYJ最开始在的区域(‘C’表示).
  3. 这是FPJ在的区域(‘F’表示).
  4. 空区域(‘.’表示).

CYJ携带了一个所谓传送枪的东西,这是一把可以创造传送门的枪械,在每一次行动中,他可以选择下列操作中的一项:

  1. 1.移向一个相邻的格子中(,,,,不能移到墙在的格子里).这个操作要消耗一个单位的时间.
  2. 2.转向一个墙(不需要相邻,只需面向即可),向其发射传送门,传送门会留在墙内面向你的地方(至多只能同时存在两扇传送门),若墙上已经有两扇传送门,而你发射了第三扇,那么最初发射的那一扇会消失。同时,你无法在一个位置制造两扇传送门(这个操作不会耗费时间)。
  3. 3.如果他与一块墙壁相邻且面前有一扇传送门,那么他可以移动到另一扇传送门前方的格子。这个操作会耗费一个单位的时间.

CYJ想要知道自己最少需要多少时间才能够从起点(‘C’)到达终点(‘F’).

请注意:我们保证地图边缘会是一圈墙壁且一定存在‘C’,‘F’.

【输入】

第一行输入两个正整数 N M ,(4   N,M ≤ 500).表示地图大小。

接下来的N行每行一个长度为M的字符串.表示地形。

输出】

    你需要输出最少的到达终点的时间,如果不能到达请输出”no”

【输入输出样例】

样例1

样例2

样例3

cell.in

4 4

####

#.F#

#C.#

####

cell.out

2

cell.in

6 8

########

#.##..F#

#C.##..#

#..#...#

#.....##

########

 

cell.out

4

cell.in

#####

#C#.#

###F#

#####

 

cell.out

no

样例2解释:

C(3,2)开始,我们首先向左发射传送门,再向下发射传送门,向左进入传送门,到达(5,2),向右发射传送门,向下进入传送门,到达(5,6),向上发射传送门,向右进入传送门,到达(2,6),向右移动,到达F.

 

【数据范围】

50%的数据满足:4<= N,M <=15

    100%的数据满足:4<= N,M <=500

这道题也不会……考试的时候瞎写一通骗了15pts。

那这题该怎么做呢……我们考虑之后发现,一个人在一个格子最多只能有八种行为:四种是向上下左右走一格,还有四种是向四个方向的墙上发射传送门,之后跑到最近的墙旁边传送过去。所以说,我们可以首先暴力的预处理出来对于每个点最近的墙在哪。

这个使用bfs,一开始把所有的墙全部存在里面进行向四个方向的宽搜。(把墙全部存起来是非常优秀的操作,可以有效防止超时,就好比你同时把两个点压入set(pq)跑dij,复杂度还是nlogn,比每一个点跑一次快很多)之后在暴力预处理出来对于每一个格子其四个方向最近的墙的位置(这个有点像悬线法,就是如果你左边有墙,那么就把最近的位置设为左边点,否则直接把再左边的答案赋过来)之后再建图,对于每一个点把所有的情况都建上,最后直接跑dij。

复杂度O(nmlog(nm)),可以过。然后注意每个点最多连八条边,这样的话边数开到4e6才好。

这种题以前还真没见过……应该练习了。

看一下代码。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<queue>
#include<set>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar('\n')
#define pr pair<int,int> 
#define mp make_pair
#define fi first
#define sc second

using namespace std;
typedef long long ll;
const int M = 505;
const int N = 500005;
const int INF = 100000009;

int read()
{
    int ans = 0,op = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-') op = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        ans *= 10;
        ans += ch - '0';
        ch = getchar();
    }
    return ans * op;
}

struct node
{
    int next,to,v;
}e[N<<3];

int n,m,lw[M][M],rw[M][M],dw[M][M],uw[M][M],S,T,bs[M][M],ecnt,head[N],dis[N];
int dx[5] = {0,1,0,-1},dy[5] = {1,0,-1,0};
char s[M][M];
queue <pr> q;
set <pr> pq;
set <pr> :: iterator it;
int id(int x,int y)
{
    return (x-1) * m + y;
}

void ins(int x,int y,int d)
{
    if(s[x][y] == '.' && !bs[x][y]) bs[x][y] = d,q.push(mp(x,y));
}

void add(int x,int y,int z)
{
    e[++ecnt].to =y;
    e[ecnt].v = z;
    e[ecnt].next = head[x];
    head[x] = ecnt;
}

void bfs()
{
    while(!q.empty())
    {
        int kx = q.front().fi,ky = q.front().sc;
        q.pop();
        rep(i,0,3) ins(kx+dx[i],ky+dy[i],bs[kx][ky]+1);        
    }
}

void init()
{
    rep(i,1,n)
    rep(j,1,m)
    {
        if(s[i][j] != '.') continue;
        if(s[i][j-1] == '#') lw[i][j] = id(i,j);
        else lw[i][j] = lw[i][j-1];
        if(s[i-1][j] == '#') uw[i][j] = id(i,j);
        else uw[i][j] = uw[i-1][j];
    }
    per(i,n,1)
    per(j,m,1)
    {
        if(s[i][j] != '.') continue;
        if(s[i][j+1] == '#') rw[i][j] = id(i,j);
        else rw[i][j] = rw[i][j+1];
        if(s[i+1][j] == '#') dw[i][j] = id(i,j);
        else dw[i][j] = dw[i+1][j];
    }
}

void build()
{
    rep(i,1,n)
    rep(j,1,m)
    {
        if(s[i][j] != '.') continue;
        if(s[i][j+1] == '.') add(id(i,j),id(i,j+1),1),add(id(i,j+1),id(i,j),1);
        if(s[i+1][j] == '.') add(id(i,j),id(i+1,j),1),add(id(i+1,j),id(i,j),1);
        if(lw[i][j] != id(i,j)) add(id(i,j),lw[i][j],bs[i][j]);
        if(rw[i][j] != id(i,j)) add(id(i,j),rw[i][j],bs[i][j]);
        if(dw[i][j] != id(i,j)) add(id(i,j),dw[i][j],bs[i][j]);
        if(uw[i][j] != id(i,j)) add(id(i,j),uw[i][j],bs[i][j]);
    }
}

void dij()
{
    rep(i,1,n*m) dis[i] = INF;
    dis[S] = 0;pq.insert(mp(dis[S],S));
    while(!pq.empty())
    {
        pr now = *(pq.begin());
        pq.erase(pq.begin());
        for(int i = head[now.sc];i;i = e[i].next)
        {
            if(dis[e[i].to] > dis[now.sc] + e[i].v)
            {
                it = pq.find(mp(dis[e[i].to],e[i].to));
                if(it != pq.end()) pq.erase(it);
                dis[e[i].to] = dis[now.sc] + e[i].v;
                pq.insert(mp(dis[e[i].to],e[i].to));
            }
        }
    }
}

int main()
{
    n = read(),m = read();
    rep(i,1,n) 
    {
        scanf("%s",s[i]+1);
        rep(j,1,m) 
        {
            if(s[i][j] == 'C') s[i][j] = '.',S = id(i,j);
            if(s[i][j] == 'F') s[i][j] = '.',T = id(i,j);
            if(s[i][j] == '#') q.push(mp(i,j));
        }
    }
    bfs(),init(),build(),dij();
    if(dis[T] == INF) printf("no\n");
    else printf("%d\n",dis[T]);
    return 0;
}

T3.column

这题还不会……明天继续……

猜你喜欢

转载自www.cnblogs.com/captain1/p/9658352.html
今日推荐