Codeforces Round #339 (Div. 2)
题目链接:https://codeforces.com/contest/614
A - Link/Cut Tree
这题目的title有点吓人啊。
题意:给一个区间 \([l,r](1\leq l \leq r \leq 10^{18})\) 和一个数 \(k(1\leq k \leq 10^9)\) ,问 \([l,r]\) 中 \(k\) 的幂分别是。
小心溢出。
ll ans[1005];
int atop;
void test_case() {
ll l, r, k, p = 1;
scanf("%lld%lld%lld", &l, &r, &k);
atop = 0;
while(p <= r) {
if(p >= l)
ans[++atop] = p;
if(r / p < k)
break;
else
p *= k;
}
if(atop == 0)
puts("-1");
else {
for(int i = 1; i <= atop; ++i)
printf("%lld%c", ans[i], " \n"[i == atop]);
}
}
B - Gena's Code
题意:给 \(n(1\leq n \leq 10^5)\) 个数字,其中至少 \(n-1\) 个数字满足只有最高位是1,其他位都是0。求他们的乘积。
题解:找出那个可能存在的与众不同的数,在它后面加上0。
char s[100005];
char t[100005];
void test_case() {
int n;
scanf("%d", &n);
int sum0 = 0, mett = 0;
for(int i = 1; i <= n; ++i) {
scanf("%s", s);
int l = strlen(s);
int suc = 1, cnt1 = 0, cnt0 = 0;
for(int j = 0; j < l; ++j) {
if(s[j] == '1') {
++cnt1;
if(cnt1 >= 2) {
suc = 0;
break;
}
} else if(s[j] != '0') {
suc = 0;
break;
} else
++cnt0;
}
if(cnt0 == l) {
puts("0");
return;
}
if(!suc) {
strcpy(t, s);
mett = 1;
} else
sum0 += cnt0;
}
if(mett)
printf("%s", t);
else
putchar('1');
while(sum0--)
putchar('0');
putchar('\n');
}
C - Peter and Snow Blower
题意:一台凸多边形的除雪机,凸多边形按逆时针顺序给出,把它栓在一根柱子P上,然后它会绕着柱子旋转,把覆盖到的位置的雪擦除。问擦除的雪的面积。
题解:显然擦除的面积是一个圆环,且外沿必定是离P最远的凸多边形的顶点,但是内沿有可能是凸多边形的某条边绕P旋转一次得到的。所以求出每条边到P的距离,取最小值就可以得到内沿半径。最后根据 \(S=\pi R^2\) 求出圆的面积然后作差。
突然发现没有计算几何的模板。
double x[100005], y[100005];
struct Point {
double x, y;
Point (double xx = 0, double yy = 0) {
x = xx;
y = yy;
}
friend Point operator-(const Point &a, const Point &b) {
return Point(a.x - b.x, a.y - b.y);
}
double norm() {
return sqrt(x * x + y * y);
}
};
int cmp(double x) {
if(x > 1e-9)
return 1;
if(x < 1e-9)
return -1;
return 0;
}
double dot(Point a, Point b) {
return a.x * b.x + a.y * b.y;
}
double det(Point a, Point b) {
return a.x * b.y - a.y * b.x;
}
double dist(Point a, Point b) {
return (a - b).norm();
}
double dis_point_segment(const Point p, const Point s, const Point t) {
if(cmp(dot(p - s, t - s)) < 0)
return (p - s).norm();
if(cmp(dot(p - t, s - t)) < 0)
return (p - t).norm();
return fabs(det(s - p, t - p)) / dist(s, t);
}
void test_case() {
int n;
double X, Y;
scanf("%d%lf%lf", &n, &X, &Y);
double R = 0, r = 1e18;
for(int i = 1; i <= n; ++i) {
scanf("%lf%lf", &x[i], &y[i]);
R = max(R, (X - x[i]) * (X - x[i]) + (Y - y[i]) * (Y - y[i]));
}
R = sqrt(R);
Point O(X, Y);
for(int i = 2; i <= n; ++i)
r = min(r, dis_point_segment(O, Point(x[i], y[i]), Point(x[i - 1], y[i - 1])));
r = min(r, dis_point_segment(O, Point(x[1], y[1]), Point(x[n], y[n])));
printf("%.12f\n", (R * R - r * r)* acos(-1.0));
}
后面写了自己的计算几何模板之后可以用来验证一下。
*D - Skills
很有收获的一道题,提醒自己要考虑前后步骤的影响。
题意:一个游戏角色,有 \(n(1\leq n \leq 200000)\) 种属性值,每种属性值都是同一个上限 \(A(1\leq A \leq 10^9)\) 。游戏角色的价值取决于其“练满”的属性的个数和最低的属性值的值。公式为 \(value = (c_f*\sum\limits_{i=1}^{n}[a_i=A])+(c_m*\min\limits_{i=1}^{n}a_i)\) 。现在你可以加 \(m\) 个点,最大化你的角色的价值。
题解:很简单的一个贪心策略,排序之后肯定往两头点,可以枚举点满的属性的个数,剩下的点全部用来提升最小值。但是实现的时候需要注意一些细节。考虑一种分配方式,要求使得分配结果最小值为M,那么先把<M的全部提升到M,然后再贪心把最大的一些值提升到A,注意这里有可能会把M的值提升到A!一开始的实现就错在这里,忽略了两边的互相影响。由这个观察可知A的来源一定是本来就比较大的这些值,而最小值的来源不能是已经被记为A的值(为这些本身<M但是提升到A的属性再付费一次变成M就错了)。复杂度 \(O(nlognlogA)\) 但是应该跑不满。
总结:注意前后步骤之间的互相影响。
int n, A, cf, cm;
ll m;
pii a[100005];
int aa[100005];
ll prefix[100005];
bool check(int minval, ll resm, int cn) {
int pos = upper_bound(aa + 1, aa + 1 + cn, minval) - aa - 1;
if(1ll * pos * minval - prefix[pos] > resm)
return false;
return true;
}
int bs(ll resm, int cn) {
int L = aa[1], R = A;
while(1) {
int M = (L + R) >> 1;
if(L == M) {
if(check(R, resm, cn))
return R;
assert(check(L, resm, cn));
return L;
}
if(check(M, resm, cn))
L = M;
else
R = M - 1;
}
}
ll calc_ans(int cntA) {
if(1ll * A * cntA - (prefix[n] - prefix[n - cntA]) > m)
return -LINF;
ll resm = m - (1ll * A * cntA - (prefix[n] - prefix[n - cntA]));
ll tmpans = 1ll * cntA * cf;
int minval = bs(resm, n - cntA);
tmpans += 1ll * minval * cm;
return tmpans;
}
int ans[100005];
void gen_ans(int cntA) {
if(1ll * A * cntA - (prefix[n] - prefix[n - cntA]) > m)
exit(-1);
ll resm = m - (1ll * A * cntA - (prefix[n] - prefix[n - cntA]));
ll minval = bs(resm, n - cntA);
for(int i = 1; i <= n; ++i) {
if(a[i].first < minval)
ans[a[i].second] = minval;
else
ans[a[i].second] = a[i].first;
}
for(int i = n; cntA--; --i)
ans[a[i].second] = A;
for(int i = 1; i <= n; ++i)
printf("%d%c", ans[i], " \n"[i == n]);
}
void test_case() {
scanf("%d%d%d%d%lld", &n, &A, &cf, &cm, &m);
ll sumx = 0;
for(int i = 1, x; i <= n; ++i) {
scanf("%d", &x);
a[i] = {x, i};
aa[i] = x;
sumx += x;
}
if(1ll * A * n - sumx <= m) {
ll ans = 1ll * n * cf + 1ll * A * cm;
printf("%lld\n", ans);
for(int i = 1; i <= n; ++i)
printf("%d%c", A, " \n"[i == n]);
return;
}
sort(a + 1, a + 1 + n);
sort(aa + 1, aa + 1 + n);
for(int i = 1; i <= n; ++i)
prefix[i] = prefix[i - 1] + aa[i];
ll ans = -LINF;
int cntA = -1;
for(int i = 0; i < n; ++i) {
ll tmpans = calc_ans(i);
if(tmpans > ans) {
ans = tmpans;
cntA = i;
}
}
printf("%lld\n", ans);
gen_ans(cntA);
}
思考:假如改变枚举cntA的顺序,是否可以缩小bs那次二分的范围呢?现在是A越买越多,虽然硬币越来越少但是需要提升到最小值M的属性的数量也有可能下降。不过由于cntA的数量不同导致需要提升为M的数量也不同,加这个优化估计是得不偿失。