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(); }
相关文章推荐
- 【JZOJ4848】【GDOI2017模拟11.3】永恒的契约
- 4848. 【GDOI2017模拟11.3】永恒的契约
- 【GDOI2017模拟11.3】永恒的契约
- 【GDOI2017模拟11.3】永恒的契约
- 【jzoj4848】【永恒的契约】【单调栈】
- GDOI2017模拟11.3 总结
- 4850. 【GDOI2017模拟11.3】记忆的轮廓
- JZOJ4849. 【GDOI2017模拟11.3】记忆的轮廓 期望+答案上界剪枝
- [WerKeyTom_FTD的模拟赛]永恒的契约
- JZOJ4848 永恒的契约
- JZOJ4857. 【GDOI2017模拟11.4】Tourist Attractions
- GDOI2017模拟11.5 总结
- 【GDOI2017模拟11.2】相位幻击
- 【GDOI2017第二轮模拟day2】开房间
- hdu 5033 模拟+单调优化
- JZOJ4769 【GDOI2017模拟9.9】graph CDQ分治+用按秩合并维护带撤销的并查集(BZOJ 4025)
- 【NOIP2015模拟11.3】备用钥匙
- 【NOIP2015模拟11.3】IOIOI卡片占卜
- 永恒的契约
- 【GDOI2017模拟11.5】Market