扑克牌
/贪心/堆/二分查找/
Solution
可以看到洛谷和一本通上的数据范围有微妙的区别...其中洛谷的算法(算法二)是通用算法,而一本通的算法(算法一)只能过它自己的数据。
算法一
每次贪心地把 joker 给当前排数最少的堆,直到没有 joker 或有牌用完,优先队列维护。设排数为 m,时间复杂度约 O(mlogm)。
算法二
首先二分,当前答案为 mid。把 joker 看成一堆牌,问题变成了从 n+1 堆牌里面取出 n 个,能否取 mid 次。枚举 1-n,若 (a_i)<mid,统计 (a_i) 与 mid 的差值和 res,这些需要用 joker 补上。若 res <= min(x,m) 则 return true;否则 return false。
Code
算法一 Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 2333333;
int n, m, a[N], cnt = 0;
priority_queue <int> q;
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]), q.push(-a[i]);
while(!q.empty())
{
int a = -q.top();
q.pop();
if(cnt < m) a++;
q.push(-a);
if(-q.top() - cnt <= 0) break;
cnt++;
}
printf("%d", cnt);
return 0;
}
算法二 Code
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
long long n, m, a[23333], l = 0, r = 1e9;
bool check(long long x)
{
long long res = 0;
for(int i = 1; i <= n; i++)
if(a[i] - x < 0) res += a[i] - x;
return (-res) <= min(m, x);
}
int main()
{
scanf("%lld%lld", &n, &m);
for(int i = 1; i <= n; i++) scanf("%lld", &a[i]);
while(l + 1 < r)
{
long long mid = (l + r) >> 1;
if(check(mid)) l = mid;
else r = mid;
}
printf("%lld", l);
return 0;
}
楼间跳跃
Solution
好不容易做出来一题 QwQ
这题跟钓鱼那题很像...最优情况下肯定不会从 i+1 号楼走到 i 号楼(即不会往回走),考虑枚举最远到达的楼号 i,那么首先要走 i 步,≤i 的楼都要拿到 h[i] 的价值,可以前缀和维护。经过手玩发现一个神奇的性质,即在同一栋楼 i 中通过走路和坐滑梯,可以在这栋楼中到达任意的 k 层,并且到达时当前楼层中一定能拿到价值(不会浪费时间),然后到达 i+1 号楼。
所以可以用优先队列来维护前面 m-i 个最大值之和作为额外价值。由于数据范围很大,需要整栋楼地入队和出队。时间复杂度约为 O(nlogn)。
Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#define LL long long
using namespace std;
const int N = 2333333;
struct node
{
LL w, siz;
friend bool operator < (node x, node y)
{
return x.w > y.w;
}
};
LL n, m, p, Ans = 0, Num = 0, Size = 0, h[N], v[N], sum[N];
priority_queue <node> q;
inline LL read()
{
char ch = getchar(); LL x = 0, f = 1;
while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar(); }
while(ch >= '0' && ch <= '9') { x = x * 10 + ch - 48; ch = getchar(); }
return f * x;
}
int main()
{
n = read(), m = read();
sum[0] = 0, m++;
for(int i = 1; i <= n; i++) h[i] = read(), v[i] = read();
for(int i = 1; i <= n; i++) sum[i] = sum[i - 1] + v[i];
for(int i = 1; i <= n; i++)
{
if(m - i < 0) break;
q.push((node) { v[i], h[i] - 1 });
Num += v[i] * (h[i] - 1);
Size += h[i] - 1, p = Size - (m - i);
while(p > 0)
{
node x = q.top();
q.pop();
if(p >= x.siz) Size -= x.siz, Num -= (x.siz * x.w), p -= x.siz;
else
{
Size -= p;
q.push((node) { x.w, x.siz - p });
Num -= p * x.w;
p = 0;
}
}
Ans = max(Ans, sum[i] + Num);
}
printf("%lld", Ans);
return 0;
}
SCOI2010 传送带
/三分/
Solution
可以把走法抽象成下面的图:
A-E-F-D,其中 E 在线段 AB 上,F 在线段 CD 上(均可与端点重合)。
不难看出 E 的位置作为自变量,与时间的关系是一个二次函数,而对于每一个 E,F 的位置又是一个二次函数。所以基本思路为三分套三分,其中 E、F 点坐标的求解等需要用到相似三角形和一次函数的部分知识。其他的一些细节在代码里。
Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
double ax, ay, bx, by, cx, cy, dx, dy, P, Q, R, eps = 1e-6;
double dis(double x1, double y1, double x2, double y2)
{
return (double)(sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)));
}
double f(double x)
{
double ex, ey;
if(dis(ax, ay, bx, by) == 0) ex = ey = 0;
else
{
ex = fabs(ax - bx) * (x / dis(ax, ay, bx, by));
ey = fabs(ay - by) * (x / dis(ax, ay, bx, by));
}
ex = max(ex, 0.0), ey = max(ey, 0.0);
if(ax < bx) ex = ax + ex;
else ex = ax - ex;
if(ay < by) ey = ay + ey;
else ey = ay - ey;
double l = 0, r = dis(cx, cy, dx, dy), res;
do
{
double m1 = l + (r - l) / 3, m2 = r - (r - l) / 3;
double fx1, fy1, fx2, fy2;
if(dis(cx, cy, dx, dy) == 0) fx1 = fx2 = fy1 = fy2 = 0;
else
{
fx1 = fabs(cx - dx) * (m1 / dis(cx, cy, dx, dy));
fy1 = fabs(cy - dy) * (m1 / dis(cx, cy, dx, dy));
fx2 = fabs(cx - dx) * (m2 / dis(cx, cy, dx, dy));
fy2 = fabs(cy - dy) * (m2 / dis(cx, cy, dx, dy));
}
fx1 = max(fx1, 0.0), fy1 = max(fy1, 0.0);
fx2 = max(fx2, 0.0), fy2 = max(fy2, 0.0);
if(cx < dx) fx1 = dx - fx1, fx2 = dx - fx2;
else fx1 = dx + fx1, fx2 = dx + fx2;
if(cy < dy) fy1 = dy - fy1, fy2 = dy - fy2;
else fy1 = dy + fy1, fy2 = dy + fy2;
double len1 = dis(ex, ey, fx1, fy1);
double len2 = dis(ex, ey, fx2, fy2);
double F1 = x / P + m1 / Q + len1 / R;
double F2 = x / P + m2 / Q + len2 / R;
if(F1 < F2) r = m2;
else l = m1;
res = F1;
} while(r - l >= eps);
return res;
}
int main()
{
cin >> ax >> ay >> bx >> by >> cx >> cy >> dx >> dy;
cin >> P >> Q >> R;
double l = 0, r = dis(ax, ay, bx, by);
do
{
double m1 = l + (r - l) / 3, m2 = r - (r - l) / 3;
if(f(m1) < f(m2)) r = m2;
else l = m1;
} while(r - l >= eps);
printf("%.2lf", f(l));
return 0;
}
独木桥
/二分/
Solution
可以先看一下洛谷上的独木桥。其中有一个重要的思路:如果把每一个人看成没有区别的点,两个人相遇然后各自转头,相当于他们互相穿过了对方。
于是我们可以通过初始向左的位置 -T,初始向右的位置 +T
算出 T 时间后小孩位置构成的集合 S。
但是这个做法不能分清具体的小孩,这就需要用到本题的另一个性质:孩子的相对位置不会发生改变。即一个孩子 i,初始时若按照位置由小到大排序,他的排名为 (rank_i),则 T 时刻后他的排名依然为 (rank_i)。因此,问题转化为在集合 S 中求出第 k 小的数。
对于每一次询问二分答案,左边右边分开排序,统计向左的集合中 pos<mid+T 和向右的集合中 pos<mid-T 的个数,求出当前排名 rk(这一过程还是可以二分)。若 rk>k,r=mid;否则 l=mid。时间复杂度约为 O(nlogn)
Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define int long long
using namespace std;
const int N = 2333333, LEN = 1e10 + 2333;
struct Child { int p, d, id; } c[N];
int n, l, r, c1 = 0, c2 = 0, K, T, Q, a[N], b[N], rk[N];
int check(int x, int T)
{
int l1 = 0, r1 = c1, a1;
while(l1 + 1 < r1)
{
int mid = (l1 + r1) >> 1;
if(a[mid] < x + T) l1 = mid;
else r1 = mid;
}
if(a[r1] < x + T) a1 = r1;
else a1 = l1;
int l2 = 0, r2 = c2, a2;
while(l2 + 1 < r2)
{
int mid = (l2 + r2) >> 1;
if(b[mid] < x - T) l2 = mid;
else r2 = mid;
}
if(b[r2] < x - T) a2 = r2;
else a2 = l2;
return a1 + a2 + 1;
}
bool cmp(Child x, Child y) { return x.p < y.p; }
signed main()
{
scanf("%lld", &n);
for(int i = 1; i <= n; i++) scanf("%lld", &c[i].p), c[i].id = i;
for(int i = 1; i <= n; i++)
{
scanf("%lld", &c[i].d);
if(c[i].d == 0) a[++c1] = c[i].p;
if(c[i].d == 1) b[++c2] = c[i].p;
}
sort(a + 1, a + c1 + 1);
sort(b + 1, b + c2 + 1);
sort(c + 1, c + n + 1, cmp);
for(int i = 1; i <= n; i++) rk[c[i].id] = i;
scanf("%lld", &Q);
while(Q--)
{
scanf("%lld%lld", &K, &T);
l = -LEN, r = LEN;
K = rk[K + 1];
while(l + 1 < r)
{
int mid = (l + r) >> 1;
if(check(mid, T) > K) r = mid;
else l = mid;
}
if(check(r, T) == K) printf("%lld
", r);
else printf("%lld
", l);
}
return 0;
}
子集
/二分/
Solution
因为元素的位置对答案没有影响,先对 a 数组进行排序。
子集的中位数有两种情况:
若子集大小为 2k,则中位数为 (a_k)。
若子集大小为 2k+1,则中位数为 (frac{a_k+a_{k+1}}{2})。
根据题面,总结出以下两条结论:
1.价值最大的子集的价值 ≥0(ans 的初值为 0)
2.子集大小一定为奇数。可以证明若子集大小为 2k,则删掉 (a_{k+1}) 一定不劣(证明在书上,看不懂 QwQ。但是可以猜想后对拍验证)
首先 O(n) 枚举中位数 x,二分 k,表示从 x 左边选出 k 个,右边选出 k 个(肯定贪心选最大的,和可以用前缀/后缀和维护)。设 check(i) 表示选择 i 的最终答案,若 check(k)<check(k-1),显然若 k 继续增大答案一定不会变优,此时 r=mid;否则 l=mid。
Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 233333;
int n, a[N], sum[N];
double Ans = 0.0;
double get(int x, int k)
{
double a1 = sum[x - k], a2 = sum[x + 1], a3 = sum[n - k + 1];
return (double)(a1 - a2 + a3) / (2 * (k * 1.0) + 1.0);
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
sort(a + 1, a + n + 1);
sum[n + 1] = 0;
for(int i = n; i >= 1; i--) sum[i] = sum[i + 1] + a[i];
for(int i = 2; i <= n; i++)
{
double x = a[i];
int l = 1, r = min(n - i, i - 1);
while(l + 1 < r)
{
int mid = (l + r) >> 1;
if(get(i, mid) < get(i, mid - 1)) r = mid;
else l = mid;
}
Ans = max(Ans, max(get(i, l) - x, get(i, r) - x));
}
printf("%.5lf", Ans);
return 0;
}
friends
/字符串哈希/
Solution
注意书中计算一段连续区间 (C' = c_{k+1}, c_{k+2}, ..., c_{k+n}) 的哈希值公式为 (H(C')=H(C, k+n)-H(C, k) * b^n)。考虑枚举插入的字符 (x),它一定把 2 个 S 中的其中一个拆成了两半,这时候我们需要根据公式把它拼起来。设前一段为 S1,长度为 l1,后一段为 S2,长度为 l2,则该段拼合后的哈希值为 (H(S1)*b^{l2}+H(S2))。
计算和讨论过程相对复杂,注意,输出 NOT UNIQUE 当且仅当存在两个不一样的 S,而不是存在两个不一样的 x 或存在两个不一样的 posx
Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
const int N = 2333333;
const long long h1 = 1e9 + 7, h2 = 1e9 + 9, b = 131;
long long n, len, pos = -1, ans1, ans2;
long long a[N], b1[N], b2[N], s1[N], s2[N];
string s, res = "";
long long get1(long long l, long long r)
{
if(r < l) return 0;
return ((s1[r] - (s1[l - 1] * b1[r - l + 1]) % h1) % h1 + h1) % h1;
}
long long get2(long long l, long long r)
{
if(r < l) return 0;
return ((s2[r] - (s2[l - 1] * b2[r - l + 1]) % h2) % h2 + h2) % h2;
}
int main()
{
cin >> n >> s;
if(n % 2 == 0)
{
printf("NOT POSSIBLE");
return 0;
}
len = (n - 1) / 2;
for(int i = 0; i < n; i++) a[i + 1] = s[i];
b1[0] = b2[0] = 1, s1[0] = s2[0] = 0;
for(int i = 1; i <= n; i++)
b1[i] = (b1[i - 1] * b) % h1,
b2[i] = (b2[i - 1] * b) % h2;
for(int i = 1; i <= n; i++)
s1[i] = (s1[i - 1] * b + a[i]) % h1,
s2[i] = (s2[i - 1] * b + a[i]) % h2;
for(int i = 1; i <= n; i++)
{
long long num1l, num2l, num1r, num2r;
if(i - 1 < len)
{
int tot = len + 1 - i;
num1l = (get1(i + 1, len + 1) + (get1(1, i - 1) * b1[len - i + 1]) % h1) % h1;
num2l = (get2(i + 1, len + 1) + (get2(1, i - 1) * b2[len - i + 1]) % h2) % h2;
num1r = get1(n - len + 1, n);
num2r = get2(n - len + 1, n);
}
else if(i - 1 > len)
{
num1l = get1(1, len);
num2l = get2(1, len);
num1r = (get1(i + 1, n) + (get1(n - len, i - 1) * b1[n - i]) % h1) % h1;
num2r = (get2(i + 1, n) + (get2(n - len, i - 1) * b2[n - i]) % h2) % h2;
}
else if(i - 1 == len)
{
num1l = get1(1, len);
num2l = get2(1, len);
num1r = get1(n - len + 1, n);
num2r = get2(n - len + 1, n);
}
if(num1l == num1r && num2l == num2r)
{
if(pos != -1 && (ans1 != num1l || ans2 != num2l))
{
printf("NOT UNIQUE");
return 0;
}
pos = i;
ans1 = num1l, ans2 = num2l;
}
}
if(pos == -1)
{
printf("NOT POSSIBLE");
return 0;
}
if(pos - 1 < len)
{
for(int i = 1; i < pos; i++) res += a[i];
for(int i = pos + 1; i <= len + 1; i++) res += a[i];
}
else for(int i = 1; i <= len; i++) res += a[i];
cout << res;
return 0;
}
A Horrible Poem
Solution
数据范围非常不友好...首先,x 是区间 ([l,r]) 的一个循环节的条件是:
-
x 是 r-l+1 的一个因数
-
([l,r]) 去掉前 x 个字符和 ([l,r]) 去掉后 x 个字符的哈希值相等,即 (H_{l+x,r}=H_{l,r-x})。
如果暴力枚举因数,根号的复杂度会超时,考虑优化:
设最小循环节长度为 len,则原区间长度 n = len * k。将 k 的质因数依次分解,每次试除 k,得到的 k' 和 len 的乘积仍是一个循环节。利用这个性质,依次用质数 i 试除 n,若除去后仍是循环节,说明 i 是 k 的一部分。将其除去,留下的就是 len。
Solution
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define int long long
using namespace std;
const int N = 2333333, h1 = 1e9 + 7, h2 = 1e9 + 9, b = 131;
int n, Q, A, B, cnt = 0, ans, tmp;
int b1[N], b2[N], s1[N], s2[N], minp[N], p[N];
string s;
void Prim()
{
memset(minp, 0, sizeof(minp));
for(int i = 2; i <= n; i++)
{
if(!minp[i]) p[++cnt] = i, minp[i] = i;
for(int j = 1; j <= cnt; j++)
{
if(i * p[j] > n || p[j] > minp[i]) break;
minp[i * p[j]] = p[j];
}
}
}
int get1(int l, int r)
{
return ((s1[r] - s1[l - 1] * b1[r - l + 1]) % h1 + h1) % h1;
}
int get2(int l, int r)
{
return ((s2[r] - s2[l - 1] * b2[r - l + 1]) % h2 + h2) % h2;
}
bool check(int l, int r, int x)
{
return (get1(l + x, r) == get1(l, r - x)) && (get2(l + x, r) == get2(l, r - x));
}
signed main()
{
cin >> n >> s;
Prim();
b1[0] = b2[0] = 1;
for(int i = 1; i <= n; i++) b1[i] = (b1[i - 1] * b) % h1;
for(int i = 1; i <= n; i++) b2[i] = (b2[i - 1] * b) % h2;
s1[0] = s2[0] = 0;
for(int i = 1; i <= n; i++) s1[i] = (s1[i - 1] * b + s[i - 1]) % h1;
for(int i = 1; i <= n; i++) s2[i] = (s2[i - 1] * b + s[i - 1]) % h2;
scanf("%lld", &Q);
while(Q--)
{
scanf("%lld%lld", &A, &B);
ans = tmp = B - A + 1;
while(tmp != 1)
{
int t = minp[tmp];
while(tmp % t == 0 && check(A, B, ans / t)) ans /= t, tmp /= t;
while(tmp % t == 0) tmp /= t;
}
printf("%lld
", ans);
}
return 0;
}