P10311 Weighted Mean Sol
一道有些复杂的构造。
不妨设加权平均数为 \(\lambda\)。通过代换,就有了 \(\sum\limits_{i=1}^n b_i \cdot (a_i - \lambda) = 0\)。因此现在变成尝试构造 \(\lambda\)。
显然的,排序对于答案的构造没有影响,因此下文中,数组默认有序。同时,记 \(c_i = a_i - \lambda\)。
n为奇数
最为简单的情况。考虑直接取中位数为 \(\lambda\),则显然的,我们可以对称的让两项为 \(0\),这里不再赘述。
而对于中间位置,直接取到 \(m\),正确性显然。
n为偶数
这里又有分讨。定义 \(L = a[n/2],R = a[n/2+1]\)。
L \(\ne\) R-1
那么考虑取 \(\lambda\) 为 \(L+1\),显然上述构造成立。
L = R-1
本题最复杂的情况。
我们如果考虑取 \(\lambda = L\),然后删除 \(R\),并让 \(c_n + 1\)。那么这样又能构造了。但是会出现三个位置相同的情况。
我们需要证明,只要 \(n\) 不为 2,答案一定存在。考虑什么情况下不合法,显然是 \(c_1,c_n\) 产生了对应,那么只要重新取 \(\lambda = R\),就一定正确了。
而对于 \(n=2\) 不合法,证明是显然的。
Code
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false);cin.tie(0),cout.tie(0)
#define File(s) freopen(s".in","r",stdin);freopen(s".out","w",stdout)
#define LL long long
#define fi first
#define se second
const int N = 1e6 + 10;
struct node{
int val,id;
};
bool cmp(node x,node y){
return x.val < y.val;
}
node a[N];
int b[N];
int tmp[N];
int n,m;
unordered_map<int,int> mp;
int ans[N];
void print(){
for(int i=1;i<=n;i++)
cout << ans[i] << " ";
cout << "\n";
}
void solve(){
cin >> n >> m;
for(int i=1;i<=n;i++){
cin >> a[i].val;
a[i].id = i;
}
sort(a+1,a+1+n,cmp);
if(n & 1){
int k = ((n+1) / 2);
b[k] = m;
int c = a[k].val;
for(int i=1;i<=n;i++)
a[i].val -= c;
for(int i=1;i<=n;i++)
b[i] = a[i].val;
b[k] = m;
reverse(b+1,b+1+n);
for(int i=1;i<=n;i++)
ans[a[i].id] = abs(b[i]);
print();
return ;
}
else{
int L,R;
L = n/2,R = L + 1;
if(n ==2 && a[L].val == a[R].val-1){
cout << -1 << "\n";
return ;
}
if(a[L].val == a[R].val-1){
vector<int> c(n,0);
vector<bool> vis(n+1,0);
int tot = 0;
int num = a[L].val;
for(int i=1;i<=L;i++){
c[++tot] = a[i].val - num;
c[tot] = abs(c[tot]);
}
for(int i=R+1;i<=n;i++)
c[++tot] = a[i].val - num;
c[tot] ++ ;
set<int> st;
st.clear();
for(int i=1;i<L;i++)
st.insert(c[i]);
for(int i=L+1;i<=tot;i++)
st.erase(c[i]);
if(!st.empty()){
for(int i=1;i<=n;i++){
if(c[i] == (*st.begin())){
b[i] = c[tot];
b[n/2+1] = b[n] = c[i];
vis[i] = vis[n/2+1] = vis[n] = 1;
st.clear();
break;
}
}
b[n/2] = m;
int nw = L+2;
for(int i=1;i<n/2;i++){
if(vis[nw]) nw ++ ;
if(vis[i]) continue;
b[nw] = c[i];
b[i] = c[nw-1];
nw ++ ;
}
for(int i=1;i<=n;i++)
ans[a[i].id] = b[i];
print();
return ;
}
reverse(a+1,a+1+n);
for(int i=1;i<=n-1;i++)
c[i] = 0;
for(int i=1;i<=n;i++)
vis[i] = 0;
tot = 0;
num = a[L].val;
for(int i=1;i<=L;i++){
c[++tot] = a[i].val - num;
c[tot] = abs(c[tot]);
}
for(int i=R+1;i<=n;i++){
c[++tot] = a[i].val - num;
c[tot] = abs(c[tot]);
}
c[tot] ++ ;
st.clear();
for(int i=1;i<L;i++)
st.insert(c[i]);
for(int i=L+1;i<=tot;i++)
st.erase(c[i]);
if(!st.empty()){
for(int i=1;i<=n;i++){
if(c[i] == (*st.begin())){
b[i] = c[tot];
b[n/2+1] = b[n] = c[i];
vis[i] = vis[n/2+1] = vis[n] = 1;
st.clear();
break;
}
}
b[n/2] = m;
int nw = L+2;
for(int i=1;i<n/2;i++){
if(vis[nw]) nw ++ ;
if(vis[i]) continue;
b[nw] = c[i];
b[i] = c[nw-1];
nw ++ ;
}
for(int i=1;i<=n;i++)
ans[a[i].id] = b[i];
print();
return ;
}
}
else{
int c = a[L].val + 1;
for(int i=1;i<=n;i++)
a[i].val -= c;
for(int i=1;i<=n;i++)
b[i] = a[i].val;
reverse(b+1,b+1+n);
for(int i=1;i<=n;i++)
ans[a[i].id] = abs(b[i]);
print();
}
}
}
int main(){
IOS;
int T;
cin >> T;
while(T -- )
solve();
return 0;
}
后记
实际上,第一篇题解是有问题的,如果头尾相配,可能会在 \(L = R-1\) 时,出现无法匹配的情况。因此,奇数的情况是不适用的。我们需要的是找出没有重复的 \(c\),然后进行匹配。这样就合法了。
可恶的题解,让我和 ds 和空气肘击半天,最后发现构造方式是有问题的。
下面是 ds 看完后给出的补充,我认为挺有价值的,可以辅助理解:
为什么原来的配对方法会失效?
当 \(L = R-1\) 时,我们取 \(\lambda = L\) 并把 \(R\) 合并到 \(n\) 上。此时,整个 \(c\) 数组的绝对值中,会有一个数值,它在负数部分出现,却在正数部分“找不到配对”。
举个例子:如果这个“独一无二”的绝对值是 \(X\),而正数部分多出来的是合并后的大正数 \(Y\)。如果强行用旧方法配对,就会迫使 \(X\) 和 \(Y\) 配对,再加上被跳过的 \(R\) 也要共享这个值,最终很可能让 \(X\) 或 \(Y\) 在答案中出现三次,导致非法。
怎么修复?
既然冲突不可避免,不如主动去解决它。我们可以找到这个冲突的“锚点”,让那个找不到配对的负数,去和合并后的大正数单独配对,然后把被跳过的 \(R\) 也拉进来共享。这样一来,这三者纠缠的问题被一次性解决。剩下的元素,正负两边就完全对称了,可以安全地用贪心配对。
如何保证至少有一个 \(\lambda\) 可行?
可以证明,对于 \(\lambda = L\) 和 \(\lambda = R\) 这两个选择,上述冲突不可能同时发生。因为如果两个都冲突,会推出 \(x_1 + x_n = -1\) 和 \(x_1 + x_n = 3\) 同时成立的矛盾。所以,至少有一个分支能顺利进行构造。
这就是我们代码里用 set 找出那个独一无二的绝对值,并优先处理它的原因。

浙公网安备 33010602011771号