HZOJ 旋转子段
作者的正解:
算法一:对于30%的数据: 直接枚举区间直接模拟,时间复杂度O(N3)。
算法二:对于60%的数据:枚举旋转中心点,然后再枚举旋转的端点, 我们可以用O(n)的预处理求前缀和记录固定点,总时间复杂度O(N2)。
算法三:对于100%的数据:假设有最优解为[i,j](i,j皆为下标,A[i],A[j]才是题目所要输出的答案)。if(A[i]!=j&&A[j]!=i),就是A[i]和A[j]经过旋转之后都没有成为不动点,那么[i+1,j-1]也是一个最优解(如果i+1>j-1,那么[1,1]也是最优解)。两端旋转之后如果都没成为不动点我们可以去掉两端,不停地去掉后得到最优解为[x,y]则有A[x]=y或者A[y]=x,或者x=y=1( [1,1]可以不参与讨论).
因此最优解一定是[min(i,A[i]),max(i,A[i])](i=1,2,3......,n)。
只需要枚举n个值就能找到最优解,由此找最优解问题变成了查询问题。假设i<=A[i],要查询的区间变成了三段[1,i-1] [i, A[i] ] [ A[i+1] ,n],固定点个数O(1)的时间查询。中间这一段 [i, A[i] ]经过了一次旋转,对于旋转后的固定点j有A[j]+j=A[i]+i且,而[j,A[j]]也是我们要考虑成为最优解的一个旋转,我们事先将这n种旋转按照轴心不同分类,每一类由旋转区间长度短到长排序。以上算法因为要排序所以复杂度为O(N log N)。
然而数据很水我们可以直接错解碾标算。
对于60的数据:
对于一个旋转中心,他的至少一个端点反转后是固定点,即满足(a[i]+i=mid*2)。于是我们可以枚举这个端点,然后就可以算出旋转中心进而On统计答案。
然后说一下我的错解(其实很接近正解了):
根据以上算法可以发现求得就是对于一个旋转中心的旋转段总(a[i]+i=mid*2)的点数以及旋转段外本来就是固定点的个数,前者可以用桶处理,后者可以用前缀和处理。总复杂度O(n)。
对于旋转段端点的处理,更新桶时处理最后保证对称即可。
for(int i=;i<=n;i++)
{
sum[i+a[i]]++;
l[i+a[i]]=min(l[i+a[i]],i);
r[i+a[i]]=max(r[i+a[i]],i);
}
for(int i=;i<=n*;i++)
{
int tl=l[i],tr=r[i];
l[i]=min(l[i],i-tr);
r[i]=max(r[i],i-tl);
}
所以到现在你发现这个算法错误的地方吗?没错上面只是处理的对于每个旋转中心最大的旋转段,但是这不一定是最优的(然而数据中好像都是最优的……),提供一组hack数据:
15
14 2 8 9 10 11 1 15 6 5 4 3 13 7 12
正确答案是7,而以上算法出的是6。错误就在于这里最大的旋转区间并不是最优的。
为了解决这个问题,就需要将最大区间不断收缩寻找最优解。
但是这样就又成$n^2$的了,怎么办呢?
其实可以发现一步一步收缩的话有很多步数是没有意义的,我们可以考虑对每个旋转中心开一个vector存a[i]+i=mid的点的位置,按照离旋转中心的距离排序,这样收缩的时候只需枚举vector,以上算法因为要排序所以复杂度为O(N log N)。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<ctime>
#define MAXN 500010
#define LL long long
#define ma(x,y) memset(x,y,sizeof(x))
using namespace std;
int n,a[MAXN];
int sum[MAXN*],l[MAXN*],r[MAXN*];
int pre[MAXN];
inline int read();
signed main()
{
// freopen("in.txt","r",stdin);
// freopen("tem","r",stdin);
// freopen("1.out","w",stdout); double sst=clock();
n=read();
for(int i=;i<=n;i++)a[i]=read();
/* if(n<=500)
{
int ans=0,res=0;
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
{
for(int k=0;i+k<=j-k;k++)
{
if(a[i+k]==j-k)res++;
if(a[j-k]==i+k)res++;
if((i+k==j-k)&&a[j-k]==i+k)res--;
}
for(int k=1;k<i;k++)
if(a[k]==k)res++;
for(int k=j+1;k<=n;k++)
if(a[k]==k)res++;
ans=max(ans,res);res=0;
}
printf("%d\n",ans);
return 0;
}
if(n<=5000)
{
int ans=0,res=0;
for(int i=1;i<=n;i++)
{
res=0;
double mid=1.0*(i+a[i])/2;
int st=min(a[i],i),en=max(a[i],i);
for(int j=st;j<=en;j++)
if(1.0*(double)(j+a[j])/2==mid)res++;
for(int j=1;j<st;j++)
if(j==a[j])res++;
for(int j=en+1;j<=n;j++)
if(j==a[j])res++;
ans=max(ans,res);
}
printf("%d\n",ans);
return 0;
}
else*/
{
for(int i=;i<=n;i++)
{
pre[i]=pre[i-];
if(a[i]==i)pre[i]++;
}
ma(l,0x7f);ma(r,);
for(int i=;i<=n;i++)
{
sum[i+a[i]]++;
l[i+a[i]]=min(l[i+a[i]],i);
r[i+a[i]]=max(r[i+a[i]],i);
}
for(int i=;i<=n*;i++)
{
int tl=l[i],tr=r[i];
l[i]=min(l[i],i-tr);
r[i]=max(r[i],i-tl);
}
int ans=;
for(int i=;i<=n*;i++)
if(sum[i])
{
ans=max(ans,sum[i]+pre[l[i]-]+pre[n]-pre[r[i]]);
/* for(int j=1;j&&l[i]<r[i];j++)
{
double st=clock();
if(l[i]+a[l[i]]==i)sum[i]--;
if(r[i]+a[r[i]]==i)sum[i]--;
l[i]++,r[i]--;
ans=max(ans,sum[i]+pre[l[i]-1]+pre[n]-pre[r[i]]);
double en=clock();
if((en-st)/1000>5)break;
if((en-sst)/1000>550)break;
}*/
}
printf("%d\n",ans);
return ;
}
}
inline int read()
{
int s=,f=;char a=getchar();
while(a<''||a>''){if(a=='-')f=-;a=getchar();}
while(a>=''&&a<=''){s=s*+a-'';a=getchar();}
return s*f;
}
所以我并没有正解的代码
HZOJ 旋转子段的更多相关文章
- 8.7 NOIP模拟测试14 旋转子段+走格子+ 柱状图
T1 旋转子段 30% 暴力枚举起点和长度,暴力判断,o(n3) 不知道为什么我拿了40分... 60% 每一个点都有一个固定的旋转中心可以转成固定点,枚举旋转点和长度. 100% 用一个vecto ...
- [CSP-S模拟测试]:旋转子段(数学)
题目描述 $ZYL$有$N$张牌编号分别为$1,2,...,N$.他把这$N$张牌打乱排成一排,然后他要做一次旋转使得旋转后固定点尽可能多.如果第$i$个位置的牌的编号为$i$,我们就称之为固定点.旋 ...
- NOIP模拟测试14「旋转子段·走格子·柱状图」
旋转子段 连60分都没想,考试一直肝t3,t2,没想到t1最简单 我一直以为t1很难,看了题解发现也就那样 题解 性质1 一个包含a[i]旋转区间值域范围最多为min(a[i],i)----max(a ...
- 【JZOJ6288】旋转子段
description analysis 可以先用前缀和把原串不调整的方案数先求出来 对于一种翻转,肯定是把\([i..a[i]]\)或\([a[i]..i]\)这段区间翻转 也可以看做是以\({i+ ...
- 旋转子段 (思维stl)
题目: 大概意思就是给你一个序列,你可以选择一段区间使它左右翻折一遍,然后呢,从1到n找一遍,看a[i]==i的数最多是多少. 其实刚才我已经把暴力思路说出来了,枚举每一个区间长度,枚举每一个左端点, ...
- CSYZDay2模拟题解
T1.rotate [问题描述] ZYL有N张牌编号分别为1, 2,……,N.他把这N张牌打乱排成一排,然后他要做一次旋转使得旋转后固定点尽可能多.如果第i个位置的牌的编号为i,我们就称之为固定点.旋 ...
- NOIP模拟 14
垃圾成绩,一点都不稳定. 如果把数组开小的分得到的话..总分还挺不错.. 那又能怪谁,都快NOIP了还犯这种傻逼错误 nc哥是要阿卡的节奏..真是太强了 某kyh也不知道偷了谁的rp,分高的一批 wd ...
- 2019.10.22 校内CSP%你赛
我太难了 先说好没有代码T1 题目大意: 给定一些形如|ax+b|的式子,求最小的x使得它们的和最小. 算法一: 大家知道零点分段法 对于这n个式子我们有n+1个取值范围 使得展开这n个式子得到的新式 ...
- [NOIP模拟14]题解
当垃圾已经成为一种常态233333 A.旋转子段 考场上的$n^2$手残少了20分,555 (主要是因为实在打不出来$n^3$的做法所以写不了对拍?ccc为什么考场上没有想起有reverse()这么 ...
随机推荐
- 深入浅析python中的多进程、多线程、协程
深入浅析python中的多进程.多线程.协程 我们都知道计算机是由硬件和软件组成的.硬件中的CPU是计算机的核心,它承担计算机的所有任务. 操作系统是运行在硬件之上的软件,是计算机的管理者,它负责资源 ...
- Linux预习
目录 linux系统和unix系统的简介 linux系统和unix系统的简介 unix是什么:和widows一样 特点:多用户,多任务 同一时刻,多用户同时执行多项程序,互不干扰 GNU项目 就是一个 ...
- SQL Server新增用户并控制访问权限设置。
新增用户: 一.进入数据库:[安全性]—>[登录名]—>[新建登录名] 二.在常规选项卡中.如图所示,创建登录名.注意设置默认的数据库. 三.在[用户映射]下设置该用户所能访问的数据库.并 ...
- select @@identity的用法 转
用select @@identity得到上一次插入记录时自动产生的ID 如果你使用存储过程的话,将非常简单,代码如下:SET @NewID=@@IDENTITY 说明: 在一条 INSERT.SELE ...
- 如何在不卸载原来jdk1.8的情况下切换到jdk1.7
将Path环境变量中的JAVA_HOME变量中写入现在的JDK1.7路径即可.
- 数据库----ORACLE和MYSQL数据库简介
一.什么是数据库? 数据库(Database---DB)按照组织.储存和管理数据的仓库.(理解以下三个概念) 数据(Data)用来描述事物的记录都可称数据,如文字音乐图像. 数据库系统(Dat ...
- 【C/C++多线程编程之七】pthread信号量
多线程编程之信号量 Pthread是 POSIX threads 的简称.是POSIX的线程标准. 相互排斥量用来处理一个共享资源的同步訪问问题,当有多个共享资源时,就须要 ...
- Linux SSH远程链接 短时间内断开
Linux SSH远程链接 短时间内断开 操作系统:RedHat 7.5 问题描述: 在进行SSH链接后,时不时的就断开了 解决方案: 修改 /etc/ssh/sshd_config 文件,找到 Cl ...
- 使用nuxt.js官方脚手架构建koa2的es6编译问题
最近在学用nuxt集成koa2做vue后台,发现官方自带脚手架搭建的koa2使用的仍是es5语法,如果想用es6怎么办呢? 这是由于自带脚手架在构建koa2时默认的nodemon是没有使用babel编 ...
- SDUT-3376_数据结构实验之查找四:二分查找
数据结构实验之查找四:二分查找 Time Limit: 30 ms Memory Limit: 65536 KiB Problem Description 在一个给定的无重复元素的递增序列里,查找与给 ...