关于一个trick
trick:动态维护环上相邻点对的值的 \(\max\) 的最大值,然后合并为 \(\max(x,y)\)。
用一个 set 维护,一开始先将每个数向前驱和后继分别连一条不同权值的边,然后先初始化前驱后继数组(咋这么像链表),然后开始类似广度优先搜索的跑图(其实一点也不相似,只是结构有点像),然后每次取出最小的,合并后,更新前驱后继即可。
时间复杂度:\(O(n \log n)\),当然带点 STL 的常数。
直接放一道同样 trick 的题目的代码(转化过程详见我的某场比赛的题解的 T3):
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5+5;
int a[N];
int l[N];
int r[N];
int vis[N];
signed main()
{
ios_base::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while(t--)
{
int n;
cin >> n;
for(int i = 0;i<n;++i)
{
cin >> a[i];
l[i] = (i-1+n)%n;
r[i] = (i+1)%n;
vis[i] = 0;
}
if(n == 1)
{
cout << 0 << '\n';
continue;
}
set<pair<int,int>>s;
for(int i = 0;i<n;++i)
{
s.insert({max(a[i],a[r[i]]),i});
}
long long sum = 0;
int x = n;
while(x>1)
{
auto it = s.begin();
int cnt = it->first;
int i = it->second;
s.erase(it);
int j = r[i];
if(vis[i]||vis[j]||r[i]!=j)
{
continue;
}
sum+=cnt;
if(a[i]>a[j])
{
vis[j] = 1;
r[i] = r[j];
l[r[j]] = i;
s.erase({max(a[l[j]],a[j]),l[j]});
s.erase({max(a[j],a[r[j]]),j});
s.insert({max(a[i],a[r[i]]),i});
s.insert({max(a[l[i]],a[i]),l[i]});
}
else
{
vis[i] = 1;
l[j] = l[i];
r[l[i]] = j;
s.erase({max(a[l[i]],a[i]),l[i]});
s.erase({max(a[i],a[r[i]]),i});
s.insert({max(a[j],a[r[j]]),j});
s.insert({max(a[l[j]],a[j]),l[j]});
}
--x;
}
cout << sum << '\n';
}
return 0;
}

浙公网安备 33010602011771号