神话三峰子任务一题解
神话三峰子任务一题解
题意
第一问交互。
给你 \(N\) 个数的数组 \(H\) ,下标从 \(0\ 到\ N-1\),\(H[i] \in [1,N-1]\)
你要求符合 \(\{d(i,j),d(i,k),d(j,k)\}=\{h[i],h[j],h[k]\}\) 的三元组 \((i,j,k)\)个数。
规定 \(d(i,j)\) 表示 \(j-i\) 且上述约束条件是可重集。
实现细节
你要实现以下函数:
long long count_triples(std::vector<int> H)
\(H:\) 长度为 N 的数组,表示每座山峰的高度。
对每个测试用例,该函数恰好被调用一次。
该函数返回一个整数 T,表示满足上述条件的三元组数量。
思路
分情况讨论:
-
\(h[i]=max\) 则 \(h[i]=k-i\) 那么 \(k=h[i]+i\) 。
这个时候k已经知道,那么可以用 \(k\) 去推 \(i,j\) 分两种情况直接 \(check\)就可以。
\(\{j-i,k-j\}=\{h[j],h[k]\}\)\[即: \ \ \ \begin{cases} k=h[i]+i \\ j=k-h[rnk2] \\ i=j-h[min] \end{cases} \ \ \ 或\ \ \ \begin{cases} k=h[i]+i;\\ j=k-h[min]\\ i=j-h[rnk2] \end{cases} \] -
\(h[k]=max\ 则\ h[k]=k-i\ 那么\ k=h[k]+i\)。
同第一种情况一样做就可以。
\(\{k-j,j-i\}=\{h[i],h[j]\}\)\[即: \ \ \ \begin{cases} k=h[k]+i\\ j=k-h[rnk2]\\ i=j-h[min] \end{cases} \ \ \ 或\ \ \ \begin{cases} k=h[k]+i\\ j=k-h[min]\\ i=j-h[rnk2] \end{cases} \] -
当 \(h[j]=max\) 是困难的,此时 \(k-i=h[j]\)
-
但是当 \(j-i=h[i],k-j=h[k]\) 是可做的。
考虑变形 \(j=i+h[i]=k-h[k]\)
处理出来 \(l[j(j=i+h[i])].pb(i),r[j(k-h[k])].pb(k)\)。枚举 \(j\),然后枚举右端点 \(r[j]\ 即\ k\) 给右端点打上 \(vis\) 标记。
再枚举左端点 \(l[j]\ 即\ i\) 利用上最开始的条件 \(k=i+h[j]\) 查 \(vis[i+h[j]]\)。
如果右端点被标记了就符合条件。
复杂度分析
这个东西复杂度看起来很像\(O(n^2)\),但是其实因为 \(\sum_{i}l_i,\sum_{i}r_i<=n\) ,均摊下来是 \(O(n)\) 的复杂度。
但是计数的时候要注意这里有坑。
如果 \(k-j=j-i\) 的情况在这里会统计两次,那么考虑坚决方法,我们知道 \(k-j=h[k],j-i=h[i]\) 那么在上面右端点被标记并且要求 \(h[k]!=h[i]\) 的时候可以直接使 \(ans\)++。
对于 \(h[k]=h[i]\) 的情况单独统计一遍,还是枚举 \(j\) ,再枚举长度 \(d\) ,限制
j-d>=1 && j+d<=n那么if(h[i]==d && h[k]==d && h[j]==2*d) ans++; -
最麻烦的是 \(j-i=h[k],k-j=h[i]\) 的情况。
考虑变形 \(\color{red}i+h[k]=k-h[i]=j\)
可以得到第一个式子:- \(i+h[k]=k-h[i]\)
然后把最开始的条件 \(k-i=h[j]\) 带入\(\color{red}{红色式子}\)消元:
- \(j+h[i]=i+h[j]\)
- \(k-h[j]=j-h[k]\)
整理式子可得:
\[\begin{cases} i+h[i]=k-h[k]\\ j-h[j]=i-h[i]\\ k+h[k]=j+h[j] \end{cases} \]对于这个情况我们直接进行三元环计数即可。
具体来说我们用 \(u_x\) 表示 \(x+h[x]\) 用 \(v_x\) 表示 \(x-h[x]\)。
上述条件就变成了:\[\begin{cases} u_i=v_k\\ v_j=v_i\\ u_k=u_j \end{cases} \]直接把 \(u_x,v_x\) 的值当点。
建图的时候直接对于 \(u_x\) 和 \(v_x\) 连无向边,进行三元环计数就是对的。
考虑为什么三元环计数是对的。
按照我们的建图方式,三元环计数统计找出的三元环,已经满足了每个点分别是上述三个等式的值,限制条件是完备的,所以 \(ans\)+=\(sum\) 就可以了。(\(sum\)就是三元环计数的结果),就像下面这个图一样,我们找出来的三元环就是满足约束的一组 \((i,j,k)\)

-
以上就是本题的所有分析,其实是分析六种情况然后分类讨论,难点在于最后一种大情况的式子整理和转化成三元环计数。
code
#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int N = 4e5+10;
typedef long long ll;
int h[N>>1];
vector<int> l[N>>1], r[N>>1];
int vis[N];
int deg[N], rnk[N];
vector<int> g[N];
vector<pair<int,int>> e;
map<int,int> id;
int tot=0;
void init(){
e.clear();
id.clear();
tot=0;
memset(vis,0,sizeof(vis));
memset(deg,0,sizeof(deg));
memset(rnk,0,sizeof(rnk));
for(int i=0;i<N>>1;i++){
l[i].clear();
r[i].clear();
}
for(int i=0;i<N;i++){
g[i].clear();
}
}
ll count_triples(vector<int> H){
init();
int n=H.size();
for(int i=1;i<=n;i++) h[i]=H[i-1];
ll ans=0;
for(int i=1;i<=n;i++){
int k=i+h[i];
if(k>n) continue;
int j=i+h[k];
if(j<k && j+h[j]==k) ans++;
if(k-h[k]!=j){
j=k-h[k];
if(i<j && j-h[j]==i) ans++;
}
}
for(int k=1;k<=n;k++){
int i=k-h[k];
if(i<1) continue;
int j=i+h[i];
if(j<k && j+h[j]==k) ans++;
if(k-h[i]!=j){
j=k-h[i];
if(i<j && j-h[j]==i) ans++;
}
}
for(int i=1;i<=n;i++){
if(i+h[i]<=n) l[i+h[i]].pb(i);
if(i-h[i]>=1) r[i-h[i]].pb(i);
}
for(int i=1;i<=n;i++){
for(auto u:r[i]) vis[u]=1;
for(auto u:l[i]){
if(u+h[i]<=n && vis[u+h[i]] && h[u+h[i]]!=h[u]) ans++;
}
for(auto u:r[i]) vis[u]=0;
}
for(int i=1;i<=n;i++){
int h1=i+h[i];
int h2=i-h[i];
if(id.find(h1)==id.end()) id[h1]=++tot;
if(id.find(h2)==id.end()) id[h2]=++tot;
h1=id[h1]; h2=id[h2];
e.pb({h1,h2});
deg[h1]++; deg[h2]++;
}
vector<int> p(tot+1);
for(int i=1;i<=tot;i++) p[i]=i;
sort(p.begin()+1,p.end(),[&](int x,int y){
return deg[x]<deg[y] || (deg[x]==deg[y] && x<y);
});
for(int i=1;i<=tot;i++) rnk[p[i]]=i;
for(auto [u,v] : e){
if(rnk[u]<rnk[v]) g[u].pb(v);
else g[v].pb(u);
}
for(int u=1;u<=tot;u++){
for(auto v:g[u]) vis[v]=u;
for(auto v:g[u]){
for(auto w:g[v]){
if(vis[w]==u) ans++;
}
}
}
return ans;
}

浙公网安备 33010602011771号