51Nod-1494 选举拉票 权值线段树
51Nod-1494 选举拉票 权值线段树
题意
现要竞选市长。有\(n\)个选民投票。
每行两个数字\(a_i,b_i\)表示第\(i\)个选民投给\(a_i\)号候选人,必须花费\(b_i\)使他投你的票。
你是第0号候选人。
问最少花多少钱使你竞选成功。
\[1\leq n \leq 10^5\\
0\leq a_i \leq 10^5,0\leq b_i \leq 10^4
\]
分析
从\(n - st到max(1,st)\)枚举要收买多少人。
假设对每个候选人票数小于\(i\)要收买\(num\)个人。那么还需要\(i - num\)个人。
显然我们贪心的选择剩下的人。
为了快速计算这些人的总和,可以建立一颗权值线段树,做到单次查询\(O(logn)\)
权值线段树的节点下标(不是真正的下标)表示权值,num表示个数,sum表示总和(方便询问)
还要注意这里如果size小于i就直接break,大大减少了循环次数。这也是之前排序的原因。
代码
priority_queue<int, vector<int>, greater<int> > q[maxn];
vector<int> id;
int vis[maxn];
int cnt;
int st;
bool cmp(int x, int y) {
return q[x].size() > q[y].size();
}
struct Node {
int num, sum;
};
Node node[maxn << 2];
void push_up(int i) {
node[i].num = node[i << 1].num + node[i << 1 | 1].num;
node[i].sum = node[i << 1].sum + node[i << 1 | 1].sum;
}
void update(int i, int l,int r,int p, ll v) {
if (l == r) {
node[i].num += v;
node[i].sum += l * v;
return;
}
int mid = l + r >> 1;
if (p <= mid) update(i << 1, l, mid, p, v);
else update(i << 1 | 1, mid + 1, r, p, v);
push_up(i);
}
int query(int i, int l, int r, int k) {
if (k <= 0) return 0;
if (node[1].num < k) return 2e9;
if (l == r) return l * k;
int mid = l + r >> 1;
if (k <= node[i << 1].num) return query(i << 1, l, mid, k);
else return node[i << 1].sum + query(i << 1 | 1, mid + 1, r, k - node[i << 1].num);
}
int main() {
int n = readint();
for (re int i = 0; i < n; i++) {
int x, y;
x = readint();
y = readint();
if (!x) {
st++;
continue;
}
q[x].push(y);
update(1, 0, 10000, y, 1);
if (!vis[x]) {
vis[x] = 1;
id.push_back(x);
}
}
sort(id.begin(), id.end(),cmp);
int ans = INF;
int res = 0, num = 0;
int mx = max(1, st);
for (re int i = n; i >= mx; i--) {
for (re int j = 0; j < id.size(); j++) {
int xx = id[j];
if (q[xx].size() < i) break;
while (q[xx].size() >= i) {
int yy = q[xx].top();
res += yy;
update(1, 0, 10000,yy, -1);
q[xx].pop();
num++;
}
}
ans = min(ans, res + query(1, 0, 10000, i - num - st));
}
Put(ans);
}