您的位置:首页 > 其它

JZOJ4848. 【GDOI2017模拟11.3】永恒的契约 断环成链+单调栈

2016-11-03 21:08 281 查看

题目大意

给你个大小为n的环,环上的每个点上有一个高度ai,如果环上的两个位置i,j能看到,那么min(ai,aj)有大于等于两点间两条路径中其中一条的最大值。

n≤106

ai≤109

解题思路

方法一

首先环上的情况很难处理,那么考虑取走ai最大的位置,断环成链。因为我们取走的是最大值,所以不会有点对(i,j)以包含i点的路径作为判断是否能看到的依据,唯一的可能就是最大值有多个的时候,这种情况下,经过不包含i点的另一条路径显然也可以互相看到。这样,我们考虑完链上的贡献后,在累计删去的i点的贡献就可以了。

那么怎么统计链上的情况。为了不算重,考虑用右端点统计答案,问题转化为固定有右端点i时,有多少个左端点可以贡献答案。先考虑左边比ai小或等于的位置j,这是的min(ai,aj)对应的肯定是位置j,为了使i到j所有数都比aj小,那么对于左端点,肯定是单调递增的。在考虑左边比aI大的数,显然只有最靠近位置i的数能贡献。综上,我们只需维护一个递减的序列,每次位置i的贡献就是退栈元素的数量,如果有比ai大的点,再加1。i点的贡献很好求,直接正着扫一次,倒着扫一次,用单调栈判断合法解就行了。复杂度是O(n)的。

方法二:

一种奇怪的方法:

当然考试的时候我并不是这样想的,我的思想是,用容斥的思想,找总左边路径贡献答案的点对数加右边答案贡献的点对数,减去两边路径贡献的点对数就是答案。两边路径动能贡献的很好求,之间找出最大值和次大值统计一下就可以了。现在考虑统计左边路径贡献答案的个数,右边也一样。

不用破环成链,直接把环复制一遍,现在我们就是要找一个区间[i,i+n]内满足条件的对数,最后除2,就是贡献。对于一个位置i,显然右边第一个数是可以的,这个要特殊讨论一下。右边第一个大于aI的数肯定是边界。跟上面一样,我们也只需找到一个单调不减的序列,那么直接维护一个不减的序列,符合要求的个数就是r−l+1。而某个位置j第一个大于aj的位置可以预处理,但现在问题是随着i向右走,可能边界会向左移这要二分来得到位置,但是这题的复杂度要求是O(n)的(虽然我加个log也过了)。那么我们考虑某个点右边第一个大于它的点,显然这只有一个,那么这就构成了一个树状的结构,对于一个i的贡献就是位置i+1的深度减边界左边符合要求最靠近点的深度。而这个点的位置可以用lca来求!而我们有知道一个O(1)用tarjan求lca的方法,这样复杂度就是O(1)了!

程序

方法一:

//ddddddpppppp
#include<cstdio>
#include<cstring>
#include<string>
#include<iostream>
#include<algorithm>
#include<cmath>

using namespace std;

#define fo(i,a,b) for (int i=a;i<=b;i++)
#define fd(i,a,b) for (int i=a;i>=b;i--)

typedef long long ll;

const int N=1100000;

int T,n,stack
,tot
,a
,b
,bz
;

int read()
{
int ret=0;
char c;
while (c=getchar(),c<'0'||c>'9');
ret=c-'0';
while (c=getchar(),c>='0'&&c<='9') ret=ret*10+c-'0';
return ret;
}

int main()
{
scanf("%d",&T);
while (T--)
{
scanf("%d",&n);
int mx=0;
fo(i,1,n) a[i]=read(),bz[i]=0,mx=max(mx,a[i]);
fo(i,1,n)
if (a[i]==mx)
{
fo(j,1,n-i) b[j]=a[j+i];
fo(j,n-i+1,n-1) b[j]=a[j-n+i];
break;
}
ll ans=0;
stack[0]=0;
fo(i,1,n-1)
{
while (stack[0]&&stack[stack[0]]<b[i])
{
ans+=tot[stack[0]];
stack[0]--;
}
if (stack[0]&&stack[stack[0]]==b[i])
{
ans+=tot[stack[0]];
if (stack[0]-1) ans++;
tot[stack[0]]++;
}
else
{
if (stack[0]) ans++;
stack[++stack[0]]=b[i];
tot[stack[0]]=1;
}
}
mx=0;
fo(i,1,n-1)
if (b[i]>=mx)
{
mx=b[i];
if (!bz[i]) ans++;
bz[i]=1;
}
mx=0;
fd(i,n-1,1)
if (b[i]>=mx)
{
mx=b[i];
if (!bz[i]) ans++;
bz[i]=1;
}
printf("%lld\n",ans);
}
return 0;
}


方法二(没加优化)

//YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;
typedef long long LL;

const int MAXN = 2e6 + 5;

struct Node {
int num, side;
Node (int a, int b) {num = a, side = b;}
Node () {}
};

Node f[MAXN * 4], d[MAXN];
int n, m, a[MAXN], l[MAXN], lg[MAXN], rmq[MAXN * 2][22], t1[MAXN], t2[MAXN];

void read(int &x) {
char ch = getchar();
while (ch < '0' || ch > '9') ch = getchar();
x = 0;
while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
}

void prepare() {
int top = 0;
memset(t1, 0, sizeof t1), memset(t2, 0, sizeof t2);
for (int i = 1; i <= m; i ++) {
while (top && a[i] >= d[top].num) t1[d[top --].side] = i;
d[++ top] = Node(a[i], i);
}
top = 0;
for (int i = 1; i <= m; i ++) {
while (top && a[i] > d[top].num) t2[d[top --].side] = i;
d[++ top] = Node(a[i], i);
}
}

LL work() {
prepare();
int l = MAXN * 2 + 1, r = MAXN * 2;
LL ans = 0;
for (int i = 1; i <= n; i ++) {
int lim = t2[i];
if (lim == 0 || lim > i + n - 1) lim = i + n - 1;
if (lim == i + 1) {
ans ++;
l = min(l + 1, r + 1);
continue;
}
int side = i + 1;
int top = 0;
bool flag = 0;
while (side != 0 && side <= lim) {
if (!flag) {
if (l <= r && f[l].side != side) {
d[++ top] = Node(a[side], side);
side = t1[side];
} else {
if (l > r) f[++ r] = Node(a[side], side);
side = t1[f[r].side];
flag =  1;
}
} else {
f[++ r] = Node(a[side], side);
side = t1[side];
}
}
for (int j = top; j; j --) f[-- l] = d[j];
int lx = l, rx = r, add = 0;
while (lx <= rx) {
int mid = (lx + rx) >> 1;
if (f[mid].side <= lim) add = mid, lx = mid + 1; else
rx = mid - 1;
}
ans += (add - l + 1);
l = min(l + 1, r + 1);
}
return ans;
}

void solve() {
scanf("%d", &n);
m = n;
for (int i = 1; i <= n; i ++) read(a[i]);
for (int i = 1; i <= n; i ++) a[++ m] = a[i];
LL ans = work();
for (int i = 1; i <= m / 2; i ++) swap(a[i], a[m - i + 1]);
ans += work();
int mx = 0;
for (int i = 1; i <= n; i ++) mx = max(a[i], mx);
int num = 0;
for (int i = 1; i <= n; i ++)
if (a[i] == mx) num ++;
ans /= 2;
if (num > 1) ans -= 1ll * num * (num - 1) / 2; else {
int mxx = 0;
for (int i = 1; i <= n; i ++) {
if (a[i] == mx) continue;
mxx = max(a[i], mxx);
}
int numm = 0;
for (int i = 1; i <= n; i ++) if (a[i] == mxx) numm ++;
ans -= numm;
}
printf("%lld\n", ans);
}

int main() {
int t;
scanf("%d", &t);
for (int i = 1; i <= t; i ++) solve();
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: