题解:P7390 「EZEC-6」造树
思路
我们将当前的点划分为两个集合
每次连一条边等价于合并两个连通块,不难发现连通块
初始每个点都是独立的连通块,每次要么从
由于题目保证一定有解,这么做一定是对的。考虑构造一个合并顺序,使得价值和最大。
这里定义一个连通块的权值为连通块内剩余度数不为
一个直接的做法就是,用两个堆维护
证明
对于链的情况,贪心显然成立,降序排序
对于非链情况,考虑交换论证,对于当前连通块
如果存在一种策略不依赖于此限制且得到的答案最优,则必定存在一轮处理时,连通块
可以通过调整使得该策略满足贪心规则,调整对答案产生的变化量为:
由于
综上,贪心思路正确。
维护
直接拿堆维护是
初始
由于
此时四个队列仅剩两个元素,且其剩余度数皆为
上述过程每个节点入队、出队次数为
瓶颈在排序,总复杂度
代码
#include<bits/stdc++.h>
#define cin_fast ios::sync_with_stdio(false) , cin.tie(0) , cout.tie(0)
//#define int long long
#define in(a) a = read()
#define PII pair<int , int>
using namespace std;
typedef long long ll;
const int N = 1e7 + 5 , mod = 998244353;
const int inf = 0x3f3f3f3f;
const long long INF = 0x3f3f3f3f3f3f3f3f;
inline int read() {
int x = 0;
char ch = getchar();
bool f = 0;
while('9' < ch || ch < '0') f |= ch == '-' , ch = getchar();
while('0' <= ch && ch <= '9') x = (x << 3) + (x << 1) + ch - '0' , ch = getchar();
return f ? -x : x;
}
int n;
struct node{
int d , w;
}a[N];
bool cmp(node x , node y) {
return x.w > y.w;
}
unsigned seed;
unsigned rnd(unsigned x){
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
return x;
}
int rad(int x , int y){
seed = rnd(seed);
return seed % (y - x + 1) + x;
}
void init_data(){
in(n) , in(seed);
for(int i = 1 ; i <= n ; i ++) a[i].d = 1 , a[i].w = rad(1 , 500000);
for(int i = 1 ; i <= n - 2 ; i ++) a[rad(1 , n)].d ++;
}
queue<int>q1 , q2 , q3 , q4;
signed main() {
//cin_fast;
int type;
in(type);
if(type) init_data();
else {
in(n);
for(int i = 1 ; i <= n ; i ++) in(a[i].d);
for(int i = 1 ; i <= n ; i ++) in(a[i].w);
}
sort(a + 1 , a + n + 1 , cmp);
int k , s = n - 2 , tot;
for(int i = 1 ; i <= n ; i ++) {
if(a[i].d > 1) {
q1.push(i) , k = i , tot = a[i].d;
break;
}
}
for(int i = 1 ; i <= n ; i ++) {
if(a[i].d == 1) q3.push(i);
else if(i != k) q2.push(i);
}
ll ans = 0;
while(s) {
while(!q1.empty() && tot > 1 && s) {
int u = q1.front() , v = inf;
if(!q3.empty()) v = q3.front();
if(!q2.empty() && q2.front() < v) v = q2.front();
if(!q4.empty() && q4.front() < v) v = q4.front();
if(!q3.empty() && v == q3.front()) q3.pop();
else if(!q2.empty() && v == q2.front()) q2.pop();
else q4.pop();
ans += (ll)a[u].w * a[v].w;
a[u].d -- , a[v].d -- , tot -- , s --;
if(a[u].d == 0) q1.pop();
if(a[v].d > 0) q1.push(v) , tot += a[v].d;
}
if(!q1.empty()) q4.push(q1.front()) , q1.pop();
if(!q2.empty()) tot = a[q2.front()].d , q1.push(q2.front()) , q2.pop();
}
while(!q4.empty()) q3.push(q4.front()) , q4.pop();
while(!q2.empty()) q3.push(q2.front()) , q2.pop();
int x = q3.front();
q3.pop();
int y = q3.front();
cout << ans + (ll)a[x].w * a[y].w;
return 0;
}
/*
我怀念过往的点点滴滴,正如这代码中的队列交替,不经意间我已到了队列的那头,腼腆地接过配对节点的双手。
*/