分析
结论:如果存在解,则一定有一个解使得选的数是排序后的一段前缀并上一段后缀。
下文所说序列均已排序。
引理:对于一个可行解选的某个数,一定可以将其换成序列中的最小数或最大数而使得换完之后还是一个可行解。
证明:反证法。假设都不可换。
设当前选的所有数的和为 \(s\),限制为 \(\ge L\) 且 \(\le R\),所给的所有重量最大值为 \(r\),最小值为 \(l\)。要换掉的数为 \(x\)。
先考虑换成最大数,则不可换等价于 \(r - x > R - s\)。
同理不可换为最小数等价于 \(x - l > s - L\)。
由于都不可换,所以两式同时成立。于是两式相加,得 \(r - l > R - L\),与题目所给限制条件矛盾。所以必有至少一个方向可换。
证毕。
根据引理弱化可得,必然可以将可行解中的某个数变大或变小(换之后要保证在给定数中),而保证换之后仍为可行解。
那么我们随便考虑一个可行解,从左往右看选的每个数。我们肯定可以把这个数换成还未选的最大数或最小数,而且换完之后还是可行解。
于是必然可以把这个可行解中所有选的数都堆到前缀或后缀上。从而必存在一个可行解是一段前缀并上一段后缀。
于是枚举右端点,符合条件的左端点必为一段区间。搞出这个区间的左右端点,即可判断是否可行。注意不要选到重复数。记得判无解。
代码
#include <bits/stdc++.h>
using namespace std;
pair<long long, int> a[2000005];
long long pre[200005];
// first >=
int n, l, r;
vector<int> ans;
int bck[200005];
int Search1(long long x) {
int l = 1, r = n, mid, ans = n + 1;
while (l <= r) {
mid = (l + r) >> 1;
if (pre[mid] >= x)
ans = mid, r = mid - 1;
else
l = mid + 1;
}
return ans;
}
// last <=
int Search2(long long x) {
int l = 1, r = n, mid, ans = -1;
while (l <= r) {
mid = (l + r) >> 1;
if (pre[mid] <= x)
ans = mid, l = mid + 1;
else
r = mid - 1;
}
return ans;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> l >> r;
for (int i = 1; i <= n; i++) cin >> a[i].first;
for (int i = 1; i <= n; i++) a[i].second = i;
sort(a + 1, a + n + 1);
for (int i = 1; i <= n; i++) pre[i] = pre[i - 1] + a[i].first;
long long suf = 0;
for (int i = n + 1; i; --i) {
suf += a[i].first;
if (suf >= l && suf <= r) {
cout << n - i + 1 << "\n";
while (i <= n) cout << (a[i++].second) - 1 << " ";
return 0;
}
int x = Search1(l - suf);
int y = min(i - 1, Search2(r - suf));
if (x <= y) {
cout << n - i + 1 + x << "\n";
while (x) cout << (a[x--].second) - 1 << " ";
while (i <= n) cout << (a[i++].second) - 1 << " ";
return 0;
}
}
cout << 0 << "\n";
return 0;
}
标签:P6169,可行,洛谷,最大数,int,IOI2016,long,mid,ans
From: https://www.cnblogs.com/forgotmyhandle/p/18017504