声之形
胡测9 (by gtm1514)
今天出发参加清北营,感觉自己已经完全丧失写暴力的能力,正解也从来不会打,所以……
清北营要保龄了!!!
清北营要保龄了!!!
清北营要保龄了!!!
然后是比赛前的标准题目。
T1 Heavenly Blast
这是平面最近点对问题的扩展。
假设当前三角形最小周长为 \(C\) ,考虑平面上一个边长为 \(\tfrac{\sqrt{2}}{6}C\) 的正方形,由于正方形内最远点对距离为 \(\tfrac{C}{3}\) ,因此正方形内最多存在 \(2\) 个点,考虑在集合内加入一个点,发现只有与这个点的距离小于 \(\tfrac{C}{2}\) 的店可以更新答案,根据上面的结论发现这样的点只有 \(O(1)\) 个,找到这些点后暴力更新即可。
具体的,将所有点按照 \(x\) 为第一关键字, \(y\) 为第二关键字排序,维护 multiset 并按照 \(y\) 为第一关键字, \(x\) 为第二关键字排序,首先考虑更新答案,找到与当前点在 \(y\) 坐标上相差 \(\tfrac{1}{2}\) 以内的所有点,如果这个点与当前点在 \(x\) 坐标上相差大于 \(\tfrac{1}{2}\) ,则从 multiset 中删除,否则用于更新答案,容易发现每个点会被插入并删除一次,并且更新答案的操作为 \(O(n)\) 次,总复杂度为 \(O(n\log n)\) 。
code
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <set>
#include <iostream>
using namespace std;
const int max1 = 1e6;
const double inf = 3e9, eps = 1e-6;
int n;
struct Point
{
double x, y;
Point () {}
Point ( double __x, double __y )
{ x = __x, y = __y; }
bool operator < ( const Point &A ) const
{
if ( y == A.y )
return x < A.x;
return y < A.y;
}
}A[max1 + 5], B[max1 + 5];
int len;
multiset <Point> Set;
bool Cmp ( const Point &A, const Point &B )
{
if ( A.x == B.x )
return A.y < B.y;
return A.x < B.x;
}
double ans;
double Get_Dis ( const Point &A, const Point B )
{ return sqrt(( A.x - B.x ) * ( A.x - B.x ) + ( A.y - B.y ) * ( A.y - B.y )); }
int main ()
{
freopen("hb.in", "r", stdin);
freopen("hb.out", "w", stdout);
scanf("%d", &n);
for ( int i = 1; i <= n; i ++ )
scanf("%lf%lf", &A[i].x, &A[i].y);
ans = inf;
sort(A + 1, A + 1 + n, Cmp);
Set.clear();
for ( int i = 1; i <= n; i ++ )
{
len = 0;
Point lim = Point(-inf, A[i].y - ans * 0.5 - eps);
while ( true )
{
auto it = Set.lower_bound(lim);
if ( it == Set.end() || ( it -> y ) - A[i].y > ans * 0.5 )
break;
if ( A[i].x - ( it -> x ) > ans * 0.5 )
Set.erase(it);
else
B[++len] = *it, lim = Point(( it -> x ) + eps, it -> y);
}
for ( int j = 1; j <= len; j ++ )
for ( int k = j + 1; k <= len; k ++ )
ans = min(ans, Get_Dis(A[i], B[j]) + Get_Dis(A[i], B[k]) + Get_Dis(B[j], B[k]));
Set.insert(A[i]);
}
printf("%.8lf\n", ans);
return 0;
}
T2 QZKago Requiem
考虑二分答案,由于 \(f\) 单调递增,因此可以贪心的让前面的段尽可能长。
发现求解一个已知字符串的代价的最优复杂度为 \(O(|S|)\) ,因此求解一个尽可能长的字符串,我们可以先从小到大倍增,然后二分,容易发现求解代价时,字符串长度不超过最终长度的 \(2\) 倍,因此总复杂度为 \(O(n\log n\log w)\) 。
code
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>
#include <map>
#include <utility>
using namespace std;
const int max1 = 1e5;
const int inf = 0x3f3f3f3f;
int n, k, f[max1 + 5];
char s[max1 + 5];
map < pair <int, int>, long long > Map;
struct Suffix_Automaton
{
int trans[max1 * 2 + 5][2], link[max1 * 2 + 5], maxlen[max1 * 2 + 5], sum[max1 * 2 + 5], last, total;
vector <int> edge[max1 * 2 + 5];
void Clear ()
{ trans[0][0] = trans[0][1] = maxlen[0] = sum[0] = last = total = 0; link[0] = -1; return; }
void Extend ( int id )
{
int now = ++total, p = last;
trans[now][0] = trans[now][1] = 0;
maxlen[now] = maxlen[p] + 1;
sum[now] = 1;
last = now;
while ( p != -1 && !trans[p][s[id] - '0'] )
trans[p][s[id] - '0'] = now, p = link[p];
if ( p == -1 )
link[now] = 0;
else
{
int q = trans[p][s[id] - '0'];
if ( maxlen[q] == maxlen[p] + 1 )
link[now] = q;
else
{
int clone = ++total;
trans[clone][0] = trans[q][0];
trans[clone][1] = trans[q][1];
link[clone] = link[q];
maxlen[clone] = maxlen[p] + 1;
sum[clone] = 0;
while ( p != -1 && trans[p][s[id] - '0'] == q )
trans[p][s[id] - '0'] = clone, p = link[p];
link[q] = link[now] = clone;
}
}
return;
}
void Build ( int L, int R )
{
Clear();
for ( int i = L; i <= R; i ++ )
Extend(i);
for ( int i = 0; i <= total; i ++ )
edge[i].clear();
for ( int i = 1; i <= total; i ++ )
edge[link[i]].push_back(i);
return;
}
long long Dfs ( int now )
{
long long ans = 0;
for ( auto v : edge[now] )
{
ans += Dfs(v);
sum[now] += sum[v];
}
if ( now )
ans += f[sum[now]] * ( maxlen[now] - maxlen[link[now]] );
return ans;
}
}SAM;
long long Solve ( int L, int R )
{
if ( Map.find(make_pair(L, R)) != Map.end() )
return Map[make_pair(L, R)];
SAM.Build(L, R);
return Map[make_pair(L, R)] = SAM.Dfs(0);
}
bool Check ( long long x )
{
int ans = 0;
for ( int L = 1, R; L <= n; L = R + 1 )
{
R = L - 1;
for ( int len = 1; L + len - 1 <= n; len <<= 1 )
{
if ( Solve(L, L + len - 1) <= x )
R = L + len - 1;
else
break;
}
if ( R == L - 1 )
return false;
int l = R, r = min(n, R + R - L + 1);
while ( l <= r )
{
int mid = l + r >> 1;
if ( Solve(L, mid) <= x )
R = mid, l = mid + 1;
else
r = mid - 1;
}
++ans;
if ( ans > k )
return false;
}
return true;
}
int main ()
{
freopen("qz.in", "r", stdin);
freopen("qz.out", "w", stdout);
scanf("%d%d%s", &n, &k, s + 1);
for ( int i = 1; i <= n; i ++ )
scanf("%d", &f[i]);
long long L, R, pos;
L = 0, R = pos = Solve(1, n);
while ( L <= R )
{
long long mid = L + R >> 1;
if ( Check(mid) )
pos = mid, R = mid - 1;
else
L = mid + 1;
}
printf("%lld\n", pos);
return 0;
}
T3 TEmPTaTiON
设当前序列为 \(a\) ,询问为 \(x\) ,考虑到 \(a_i+x=\operatorname{not}(\operatorname{not}(a_i)-x)\) ,其中 \(\operatorname{not}\) 为按位取反操作,因此对 \(a_i\) 和 \(x\) 同时取反,最小操作次数不变,从高位向低位依次考虑,容易发现通过上述操作可以将 \(a\) 序列最高位全部变为 \(0\) ,如果此时 \(x\) 最高位为 \(0\) ,显然这一位可以不操作,如果 \(x\) 最高位为 \(1\) ,考虑找到一个最大的 \(a_i\) 并将其变为 \(2^{m-1}\) ,之后递归处理下一位。
考虑贪心选择的正确性,我们将变化体现在数轴上,一次操作实际上是将一个点向右移动 \(1\) 个单位长度,容易发现操作代价为当前值与目标值的距离,如果当前不操作最大的 \(a_i\) ,那么一定是因为 \(a_i\) 用于较低位的操作,设当前操作的为 \(a_j\) ,由于 \(a_j\le a_i\) ,因此两次操作一定存在交集,不难发现这一定不优。
考虑优化这个过程,考虑上述贪心的实质,如果 \(a_i\) 在当前位的异或和等于 \(x\) 在这一位的值,那么不进行操作,否则考虑操作一个 \(a_i\) ,如果操作的 \(a_i\) 在这一位为 \(0\) ,那么会产生 \(2^{m-1}-a_i\) 的代价,如果操作的 \(a_i\) 在这一位为 \(1\) ,那么会产生 \(a_i-(2^{m-1}-1)\) 的代价,容易发现可以预处理每一位每个数的代价并从小到大排序,查询时从高到低枚举每一位并贪心的选择较小的代价。由于考虑当前位时最多需要跳过 \(m\) 个之前选择的数,因此查询复杂度为 \(O(m^2)\) 。
code
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>
using namespace std;
const int max1 = 1e5, max2 = 60;
int n, m, q;
long long a[max1 + 5];
int sum[max2 + 5];
bool vis[max1 + 5];
struct Data
{
long long val;
int id;
bool operator < ( const Data &A ) const
{ return val < A.val; }
}d[max2 + 5][max1 + 5];
int main ()
{
freopen("mika.in", "r", stdin);
freopen("mika.out", "w", stdout);
scanf("%d%d%d", &n, &m, &q);
for ( int i = 1; i <= n; i ++ )
scanf("%lld", &a[i]);
for ( int bit = 0; bit < m; bit ++ )
{
sum[bit] = 0;
for ( int i = 1; i <= n; i ++ )
{
if ( a[i] >> bit & 1 )
d[bit][i].val = ( a[i] & ( ( 1LL << bit + 1 ) - 1 ) ) - ( 1LL << bit ) + 1;
else
d[bit][i].val = ( 1LL << bit ) - ( a[i] & ( ( 1LL << bit + 1 ) - 1 ) );
d[bit][i].id = i;
sum[bit] ^= a[i] >> bit & 1;
}
sort(d[bit] + 1, d[bit] + 1 + n);
}
long long x, ans;
while ( q -- )
{
scanf("%lld", &x);
ans = 0;
for ( int i = 0; i <= m - 1; i ++ )
for ( int j = 1; j <= min(n, m); j ++ )
vis[d[i][j].id] = false;
for ( int bit = m - 1; bit >= 0; bit -- )
{
if ( ( x >> bit & 1 ) ^ sum[bit] )
{
int now = 1;
while ( now <= n && vis[d[bit][now].id] )
++now;
if ( now == n + 1 )
ans += 1 << bit;
else
{
ans += d[bit][now].val;
vis[d[bit][now].id] = true;
if ( a[d[bit][now].id] >> bit & 1 )
{
x ^= a[d[bit][now].id];
x ^= ( 1LL << bit ) - 1;
}
else
{
x ^= a[d[bit][now].id];
x ^= 1 << bit;
}
}
}
}
printf("%lld\n", ans);
}
return 0;
}