题解 P1878 【舞蹈课】
算法思路:
设置一个Node类存储每对舞伴的左右舞者下标l,r和舞技差值w。
一开始读取所有相邻的舞伴信息,并插入到最小堆。
然后执行以下循环体,直至最小堆为空:
记录堆顶元素的舞者下标(当前配对舞者),标注当前舞者已配对,并删除堆顶元素,
然后删除所有涉及已配对舞者的堆顶元素,确保下次提取的顶点是满足条件的配对。
接下来跳过所有已配对舞者,用其两侧的舞者填补空白,查看是否能形成新的配对,
若能配对,则生成新结点,并插入到最小堆。
注意:题目要求“如果不止一对,那么最左边的那一对出列”,所以建立最小堆的时候,
不仅仅要看w值,还要比较左侧舞者的编号,编号小的靠前。
所以重载>运算符的时候要同时比较w和l的值。
#include<iostream>
#include<algorithm>
#include<math.h>
#include<queue>
using namespace std;
class Node { public:
// void PrintNode() {cout << l+1 << " " << r+1 << " --" << w << endl;}
void SetNode(int p1, int p2, int v) {l = p1; r = p2; w = v;}
int GetL() {return l;}
int GetR() {return r;}
int GetW() {return w;}
friend bool operator > (const Node &op1, const Node &op2)
{
return op1.w > op2.w || (op1.w == op2.w && op1.l > op2.l);
}
private:
int l, r; //分别表示左右两个舞伴的下标
int w;
};
int main()
{
priority_queue<Node, vector<Node>, greater<Node> > H;
Node t;
const int size = 200000;
char S[size];
int A[size];
int n;
cin >> n;
for (int i=0; i<n; i++)
{
cin >> S[i];
}
for (int i=0; i<n; i++)
{
cin >> A[i];
}
for (int i=1; i<n; i++)//不同性别的都入队
{
if (S[i] != S[i-1])
{
t.SetNode(i-1, i, abs(A[i]-A[i-1]));
H.push(t);
}
}
bool F[size] = {0}; //标注是否已经配对成功
int P[size/2][2]; //存储配对成功的舞者编号
int s = 0;
int l, r, p1, p2; //分别存储左右舞伴的下标
while (!H.empty())
{
t = H.top();
l = t.GetL();
r = t.GetR();
F[l] = F[r] = true; //标注该对舞伴已配对
P[s][0] = l + 1; //记录配对舞伴编号(下标+1)
P[s++][1] = r + 1;
do //除去重复配对的顶点,确保下次提取的顶点是满足条件的配对
{
H.pop();
if (H.empty())
break;
t = H.top();
p1 = t.GetL();
p2 = t.GetR();
} while(F[p1] || F[p2]);
//跳过已经配对的舞者
while(l >= 0 && F[l])
{
l--;
}
while(r < n && F[r])
{
r++;
}
if (l>=0 && r<n && S[l] != S[r])//有缘千里来相会
{
t.SetNode(l, r, abs(A[l]-A[r]));
H.push(t);
}
}
cout << s << endl;
for (int i=0; i<s; i++)
{
cout << P[i][0] << " " << P[i][1] << endl;
}
// system("pause");
return 0;
}