@(K ACMer)
[toc]
A . Raising Bacteria(位运算)
题意:给你一个细菌繁殖之后的数目x,问你,该这x个细菌最少是由多少个细菌繁殖而来.
分析:就是找到所有2的幂次加数,位运算即可.
Code
#include <cstdio>
#include <iostream>
#include <set>
#include <map>
#include <algorithm>
#include <stack>
#include <vector>
using namespace std;
typedef pair<int, int> pii;
typedef vector<int> vi;
typedef long long LL;
const int M = int(1e5) + 9, mod = (1e7) + 7, INF = 0x3fffffff;
int main(void) {
int x;
cin >> x;
int sum = 0;
while (x) {
if (x & 1) sum++;
x >>= 1;
}
cout << sum << endl;
return 0;
}
B. Finding Team Member(排序)
题意:有很多学生要组队,每一个人都有和其它每个人组队的值,要求每一步组队这个和值,要足够大.
分析:比赛的时候太急了,每次都到矩阵里去找最大和值…方法很落后.
其实只需要由大到小排序,然后每次取最大的检查有没有组队就好了.
My Code
#include <cstdio>
#include <iostream>
#include <set>
#include <map>
#include <algorithm>
#include <stack>
#include <vector>
#include <cstdlib>
#include <cstring>
using namespace std;
typedef pair<int, int> pii;
typedef vector<int> vi;
typedef long long LL;
const int M = int(1e5) + 9, mod = (1e7) + 7, INF = 0x3fffffff;
int n, a[409 * 2][409 * 2], b[409 * 2], c[409 * 2];
void judge(void) {
for (int i = 1; i <= 2 * n; i++) {
if (a[i][0] == 0) continue;
int maxs = -INF, key;
for (int j = 1; j < i; j++) {
if (a[i][j] > maxs && a[j][0]) {
maxs = a[i][j];
key = j;
}
}
for (int j = i + 1; j <= 2 * n; j++) {
if (a[j][i] > maxs && a[j][0]) {
maxs = a[j][i];
key = j;
}
}
c[i] = key;
}
return;
}
int main(void) {
memset(a, 0, sizeof(a));
cin >> n;
for (int i = 1; i <= 2 * n; i++) a[i][0] = 1;
for (int i = 2; i <= 2 * n; i++) {
for (int j = 1; j < i; j++) {
cin >> a[i][j];
}
}
int sum = 0;
while (sum < 2 * n) {
judge();
sum = 0;
for (int i = 1; i <= 2 * n; i++) {
if (c[c[i]] == i) {
b[i] = c[i];
b[c[i]] = i;
a[i][0] = 0;
a[c[i]][0] = 0;
sum++;
}
}
}
for (int i = 1; i <= 2 * n; i++) {
if (i != 1) cout << " ";
cout << b[i];
}
cout << endl;
return 0;
}
C. A Problem about Polyline(思维)
题意:
给出 (0, 0) – (x, x) – (2x, 0) – (3x, x) – (4x, 0) – … - (2kx, 0) – (2kx + x, x) …. 这样一条折线,和上面一点(a, b)。求可能的最小的x.
分析:
只需要分析,该点在上升段还是下降段:
- 在上升段, 那么折线必定经过(a - b, 0)点,那么(a−b)必须是x的偶数倍.找到这个最大偶数倍就可.
- 在下降段,那么折线段必定经过(a + b, 0)点,那么(a+b)必须是x的奇数倍,找到这个最大的奇数倍就可.
Code:
#include <cstdio>
#include <iostream>
#include <set>
#include <map>
#include <algorithm>
#include <stack>
#include <vector>
#include <cstdlib>
#include <cstring>
using namespace std;
typedef pair<int, int> pii;
typedef vector<int> vi;
typedef long long LL;
const int M = int(1e5) + 9, mod = (1e7) + 7, INF = 0x3fffffff;
int a, b;
double js(void) { //点在下降段
int c = a / b, i;
if (c % 2 == 0) c--;
i = c;
double t = (double(a) - i * double(b)) / (i + 1);
return t + b;
}
double os(void) { //点在上升段
double t = 1e20, te;
if (a - b == 0) t = b;
int c = (a - b) / b;
if (c % 2) c--;
if (c) te = (double(a) - double(b)) / c - double(b);
if (te < t) t = te;
return t + b;
}
int main(void) {
while (cin >> a >> b) {
double q = min(js(), os());
printf("%.13f\n", a < b ? -1 : q);
}
return 0;
}
D. “Or” Game(贪心 + 前缀后缀优化)
题意:
给你n个数,你可有k个x,可以把x乘在任意一个数上,问执行了这些乘法操作之后,这些数的异或值最大为多少?
分析:
这个问题咋一看,好像并没有任何想法.主要是k个x要怎么分配?这里贪心的把k个x分配到同一数上相乘.
主要的贪心是:为什么要把k个x乘到同一数?
可以知道一个序列或起来的结果的二进制表达的最大长度等于这个序列所有数中二进制位数的最大长度(也就是最大数的二进制长度).由于题中x总是大于等于2,所以我们只要多乘以一个x就可以把一个数的二进制表达下的位数加一,那么整个序列的或起来长度更大了,这个序列或起来的值也就更大了.所以尽可能把k个x乘在同一数上的贪心思想是正确的.
为什么不直接乘在最大的那个数上呢?因为可能有一个比最大数小,但是二进制表达的下的位数长度相同的数.(比如最大数为1111,一个比它小的数:1010)既然他们在位数上相同,那么乘以一个数之后增加的位数也相同,但是序列或起来的值到底哪个大并不一定,所以需要去枚举二进制位数等于最大数位数的所有数来乘以k个x.
这里枚举的时候
code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
#include <map>
#include <stack>
#include <vector>
#include <string>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
const int mod = int(1e9) + 7, INF = 0x3fffffff;
long long n, x, k;
long long pre[200009], suf[200009], a[200009];
int main(void) {
//cin >> n >> k >> x;
scanf("%I64d%I64d%I64d", &n, &k, &x);
long long m = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
m = max(m, a[i]);
}
pre[1] = suf[n] = 0;
for (int i = 2; i <= n; i++)
pre[i] = pre[i - 1] | a[i - 1];
for (int i = n - 1; i > 0; i--)
suf[i] = suf[i + 1] | a[i + 1];
long long temp = 1;
for (int i = 0; i < k; i++)
temp *= x;
long long maxs = 0;
for (int i = 1; i <= n; i++) {
if (a[i] >= x / 2)
maxs = max(pre[i] | suf[i] | (a[i] * temp), maxs);
}
printf("%I64d", maxs);
//cout << maxs << endl;
return 0;
}
E. Weakness and Poorness (三分 + DP)
题意:
给你n个数,定义这n个数的虚弱值为这个序列的所有连续子序列中子序列和的绝对值最大的一个.先对n个数中的每个数都减去一个数x,求能让这个虚弱值最小为多少?
分析:
首先,子序列和的最大值可以分为子序列和的最大值x,和子序列和的最小值的相反数y,然后取x,y中较大的一个就是最大子序列和了.
求最大(最小同理)子序列和是最经典的DP:
好现在问题来了,这个x该是多少?
简单地想一下,我们很容易发现x很大时,数列的虚弱值很大,x很小时数列的虚弱值很小,那么我们就会猜测,这肯定是一个单峰函数,求三分最低点即可!
确实是这样….但是除了好的直觉我们还需要理论
解释见官方题解,容易理解.
好了然后就是三分了…
这里wa了好多次,就是精度控制不好,以后统一采用循环150次来做!
code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
#include <map>
#include <stack>
#include <vector>
#include <string>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
const int mod = int(1e9) + 7, INF = 0x3fffffff;
int a[200009], n;
double b[200009], sum;
double cal(double x) {
b[0] = a[0] - x;
double maxs = a[0] - x, mins = a[0] - x;
for (int i = 1; i < n; i++) {
b[i] = max(b[i - 1] + a[i] - x, a[i] - x);
maxs = max(b[i], maxs);
}
for (int i = 1; i < n; i++) {
b[i] = min(b[i - 1] + a[i] - x, a[i] - x);
mins = min(b[i], mins);
}
return max(abs(maxs), abs(mins));
}
double ternary_search(void) {
double l = -sum, r = sum, x = 0, y = 10;
//cout << l << " " << r << endl;
for (int i = 0; i < 200; i++){
//cout << l << " " << r << endl;
double mid = (l + r) / 2, midmid = (mid + r) / 2;
//cout << mid << " " << midmid << endl;
x = cal(mid), y = cal(midmid);
//cout << x << " " << y << endl;
if (x > y) l = mid;
else r = midmid;
}
return min(x, y);
}
int main(void) {
while (~scanf("%d", &n)) {
sum = 0;
for (int i = 0; i < n; i++) {
scanf("%d", &a[i]);
//a[i] = 1000;
sum = max(sum, (double)abs(a[i]));
}
printf("%.10f", ternary_search());
}
return 0;
}