8.28 模拟题 T3 随机
对应题目 : JZOJ 100036【NOIP2017提高A组模拟7.10】随机
这道题题目的意思不难理解,主要是理解了之后自己的一个想法。
题目是说一个序列有一个规定的特征值:一个序列中任意两元素差的绝对值的最小值和区间长度两者之间的较大者。(表达能力棒棒哒)
然后我们在一个给定的序列中找出其中特征值最小的连续序列。
具体来说,求解的关键在于我们需要简化寻找特征值。
那么我们观察一个任意序列,我们从中任意选取一段区间,那么我们在将其向右扩张的时候,差的绝对值是有可能会减少(肯定不会增大,因为之前就已经是当前区间的最小值,如果再次增添元素进去的话,那么如果要更新只能变小更新),而与此同时区间长度是在增加的。所以,我们会面临一种临界条件,也就是差的绝对值在某一结点处已经小于等于区间长度时,我们不再考虑向右扩张而是将左边界往右靠。(这里补充一下,题目要求的是特征值最小的连续序列,而特征值是差与区间长度的较大者,也就说此时我们再往右走的话只会使这个序列的特征值变大,从而导致这不再是一个最优序列。)
看到这里,我们已经说完了基本的要求那么具体实现呢?
本来建议的是使用multiset进行维护的,但是呢,其实用线段树也是可以的,还要稍微快那么一丢丢。
这里使用的是权值线段树。
顺带呢给自己一个离散化。
现在就是开始写了。
当写权值线段树时,请牢记:我是在写权值线段树,线段树上结点的位置是权值!所以当我们合并区间时,当前区间的 差的绝对值的最小值(以下简称答案) 无非有三种情况(分治思想,返璞归真):
1>继承左儿子的答案
2>继承右儿子的答案
3>右儿子中出现过的最小值减去左儿子中出现过的最大值
更新当前区间的最小值和最大值也很简单:
1>如果左儿子有最小值,当前区间的最小值就是左儿子的最小值(请记住这是权值线段树);如果没有,那就是右儿子的最小值;如果还是没有,就不管了。
2>最大值同理,我就不说了。。。
边界情况:
修改时:当区间变成点时,进行对应修改。如果当前位置没有数或者只有一个数,那么答案为INF,否则答案为0。如果当前位置没有数,那么最小值和最大值都为0(NULL),否则为当前位置(记住这是权值线段树)。
查询时:直接查询值域区间的答案即可,即tree[1].ans。
#pragma GCC optimize("O2")
#pragma G++ optimize("O2")
#include<iostream>
#include<cstdio>
#include<algorithm>
#define maxn 1000010
#define INT long long
#define INF 0x7fffffff
using namespace std;
inline INT read()
{
INT data=0,w=1;
char ch=0;
while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0' && ch<='9') data=data*10+ch-'0',ch=getchar();
return data*w;
}
inline void write(INT x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
INT n, N;
INT A[maxn];
INT disc[maxn];
INT ans = INF;
void discretize()
{
for(int i = 1; i <= n; i++)
{
disc[i] = A[i];
}
std::sort(disc + 1, disc + 1 + n);
N = std::unique(disc + 1, disc + 1 + n) - (disc + 1);
for(int i = 1; i <= n; i++)
{
A[i] = std::lower_bound(disc + 1, disc + 1 + N, A[i]) - disc;
}
}
class SegTree
{
#define PARAM INT node = 1, INT l = 1, INT r = N
#define DEF INT mid = (l + r) >> 1; INT lc = node << 1; INT rc = lc | 1;
struct Node
{
INT minPos;
INT maxPos;
INT minSub;
INT contain;
Node() : minSub(INF >> 1) {}
} tree[maxn * 4];
INT g_Pos, g_Val;
void update(PARAM)
{
DEF;
tree[node].minSub = std::min(tree[lc].minSub, tree[rc].minSub);
if(tree[lc].maxPos && tree[rc].minPos)
tree[node].minSub = std::min(tree[node].minSub, disc[tree[rc].minPos] - disc[tree[lc].maxPos]);
tree[node].minPos = tree[lc].minPos;
if(!tree[node].minPos) tree[node].minPos = tree[rc].minPos;
tree[node].maxPos = tree[rc].maxPos;
if(!tree[node].maxPos) tree[node].maxPos = tree[lc].maxPos;
}
void add_(PARAM)
{
if(l == r)
{
tree[node].contain += g_Val;
if(tree[node].contain) tree[node].minPos = tree[node].maxPos = l;
else tree[node].minPos = tree[node].maxPos = 0;
if(tree[node].contain >= 2) tree[node].minSub = 0;
else tree[node].minSub = INF;
return;
}
DEF;
if(g_Pos <= mid) add_(lc, l, mid);
if(g_Pos > mid) add_(rc, mid + 1, r);
update(node, l, r);
}
public:
INT query()
{
return tree[1].minSub;
}
void insert(INT x)
{
g_Pos = x;
g_Val = 1;
add_();
}
void erase(INT x)
{
g_Pos = x;
g_Val = -1;
add_();
}
} st;
int main()
{
n = read();
for(int i =1; i <= n; i++)
{
A[i] = read();
}
discretize();
INT l = 1;
st.insert(A[1]);
for(int i = 2; i <= n; i++)
{
st.insert(A[i]);
INT cntSub = st.query();
ans = min(ans, max(cntSub, i - l + 1));
while(i - l > 1 && cntSub < i - l + 1)
{
st.erase(A[l]);
l ++;
cntSub = st.query();
ans = min(ans, max(cntSub, i - l + 1));
}
}
cout << ans << endl;
return 0;
}
我自己当时在做的时候其实是看错了题的,我以为必须要求是序列内的最大值与最小值,其实是任意两数对吧。但是呢,我是写了三种不同的骗分手段的,一个是直接O(n^3)模拟出来,一个是优化一点,一个是再优化一点,事后测试10分,30分,0分。额= =。
30分思路很简单,就是将原来的数据的位置关系保存下,然后排序再次扫过。任意取出两数,我们直接利用那个题目中特征值求解的公式求出特征值,然后择优更新就好。介于O(n^2)所以爆破过了前3个点。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 1000010
using namespace std;
int n,ans = 0x7fffffff;
int s[N];
struct Data
{
int Rank;
int Val;
} a[N];
int main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> a[i].Val;
s[i] = a[i].Val;
}
sort(s+1,s+n+1);
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= n; j++)
{
if(a[j].Rank) continue;
if(a[j].Val == s[i])
{
a[j].Rank = i;
break;
}
}
}
for(int i = 1; i <= n; i++)
{
int minRank = n;
for(int j = i + 1; j <= n; j++)
{
if(minRank == 1) break;
if(minRank < a[j].Rank) continue;
ans = min(ans,max(abs(a[i].Val - a[j].Val),j-i+1));
minRank = a[j].Rank;
}
}
cout << ans;
return 0;
}