我两个队友出题,我验题,总的来说题还是十分简单的,比赛定位为娱乐性质的比赛,希望最后大家都能很快乐。
总共十个可做的题,两个题应该没人提交。
最后两个题一个来自阳哥的热情贡献,是个lct板子题;另一个是听说有毕业的老学长要回来,我队友十分怂,怕被老学长快速ak,添了一个很恶心的题。这两个题被我们放在最后,怕萌新不懂事卡死在上面。
题目难度规划是三个纯签到题,学过c语言的慢慢搞应该能搞出来。4个中档题,对于现役选手和老年选手难度不是很大,都有很大希望做出来。三个较难的题,对于算法学的还不错的同学来说思路和代码实现有一定难度,主要是时间不太够。两个难度较大的题,一个题需要对数据结构有较深研究,一个题是维护出题人的尊严和欢迎远道而来的老学长,需要很深的数学研究和精细的复杂度计算。
A题 剪刀,石头,布
良心的签到题,给大一才学完c语言的萌新签到的,直接跟着模拟就行了。
注意一下花生吃完的情况。
预计所有队伍过题
#include <bits/stdc++.h>
using namespace std;
int n, s1, s2;
int main() {
//freopen("1.in", "r", stdin);
int t; scanf("%d", &t);
while(t--) {
scanf("%d%d%d",&n, &s1, &s2);
for(int i=0;i<n;i++) {
int a, b; scanf("%d%d", &a, &b);
//tie
if(a == b) continue;
//a lose
if((a == 0 && b == 1) || (a == 1 && b == 2 ) || (a == 2 && b == 0)) {
if(s1 > 0) {
s1--; s2++;
}
else {//a win
if(s2 > 0) {
s1++; s2--;
}
}
}
if(s1 > s2) {
puts("xiao Y is winner");
} else if(s1 < s2) {
puts("xiao Z is winner");
} else {
puts("tie");
}
}
}
B题 小Z的糖果店
预计3个队过题
思路可以凭直觉得到,可以反证法证明思路的正确性。代码量略长。
- 首先需要明白如果糖果店只占一个节点,问题转化为找一个节点,让其他节点到这个节点最大距离最小。那么可以肯定这个节点肯定开在树的直径上(可以用反证法证明)。
- 找出树的直径,具体方法:随便找一个点dfs得到最远的点,记为point_a,再从point_a跑dfs再次得到一个最远的点,记为point_b,因为在树上,所以从point_a到point_b有且仅有一条路径,这条路径就是树的直径。
- 从point_a到point_b这条路上遍历节点,找到一个点到point_a距离为dist_a和到point_b的距离为dist_b,max(dist_a, dist_b)最小,将这个点记录为center_point。
- 根据贪心策略从center_point跑一个dfs,得到以每一个点为根节点下面所有子节点到center_point的最大距离,然后从center_point跑一个k-1步最大权优先的bfs,逐次删去距离最大的节点,然后添加新节点。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+100;
typedef long long ll;
typedef pair<ll, ll> P;
vector <P> ve[maxn];
ll n, k, point_a, point_b, center_point, Max_len, Min, dist_to_center[maxn];
/*
n个节点,糖果店占据k个节点
point_a和point_b是树直径上的左右端点
center_point是找到树的中心,中心一定在直径上
Max_len是树直径的长度
Min记录到直径上的点到point_a和point_b的最大值最小距离
dist_to_center记录以每个点为根节点的所有子节点到center_point的最大距离
*/
vector <ll> path;//记录树的直径上包含的点
vector <P> D;//记录所有叶子节点到起始搜索节点的距离和节点标号
void init() {
D.clear();
path.clear();
Min = 1e19+100;
Max_len = 0;
scanf("%lld%lld", &n, &k);
for(int i=1;i<=n;i++) ve[i].clear();
for(int i=1;i<n;i++) {
ll a, b, c;
scanf("%lld%lld%lld",&a, &b, &c);
ve[a].push_back(make_pair(b, c));
ve[b].push_back(make_pair(a, c));
}
}
//用于找point_a and point_b
void dfs0(ll pre, ll now, ll dist) {
for(int i=0;i<ve[now].size();i++) {
ll v = ve[now][i].first;
ll d = ve[now][i].second;
if(v != pre) {
dfs0(now, v, dist + d);
}
}
if(ve[now].size() == 1) {
D.push_back(make_pair(dist, now));
}
}
void find_pointa_pointb() {
dfs0(-1, 1, 0);
sort(D.begin(), D.end());
reverse(D.begin(), D.end());
point_a = D[0].second;
D.clear();
dfs0(-1, point_a, 0);
sort(D.begin(), D.end());
reverse(D.begin(), D.end());
point_b = D[0].second;
}
//用于找树的直径上所有点
bool dfs2(ll pre, ll now, ll dist) {
path.push_back(now);
Max_len += dist;
if(now == point_b) {
return true;
}
bool flag = false;
for(int i=0;i<ve[now].size();i++) {
int v = ve[now][i].first;
if(v != pre) {
flag |= dfs2(now, v, ve[now][i].second);
}
}
if(!flag) {
Max_len -= dist;
path.pop_back();
}
return flag;
}
ll dfs3(ll pre, ll now, ll dist) {
ll MaxLen = 0;
for(int i=0;i<ve[now].size();i++) {
ll v = ve[now][i].first;
ll d = ve[now][i].second;
if(v != pre)
MaxLen = max(MaxLen, dfs3(now, v, d));
}
return dist_to_center[now] = dist+MaxLen;
}
void find_center_point() {
dfs2(-1, point_a, 0);
//从直径上找到中心点
ll dist = 0, w = 0, now = path[w];
while(true) {
for(int i=0;i<ve[now].size();i++) {
int v = ve[now][i].first;
if(v == path[w+1]) {
dist += ve[now][i].second;
if(max(dist, Max_len-dist) < Min) {
Min = max(dist, Max_len-dist);
center_point = v;
}
now = v;
break;
}
}
w++;
if(w == path.size()-1) break;
}
//get_dist_to_center()
dfs3(-1, center_point, 0);
}
//糖果店占据k-1个节点,直接根据贪心策略BFS
bool vis[maxn];
void BFS() {
for(int i=1;i<=n;i++) vis[i] = false;
priority_queue <P> qu;
for(int i=0;i<ve[center_point].size();i++) {
int v = ve[center_point][i].first;
qu.push(make_pair(dist_to_center[v], v));
vis[v] = true;
}
vis[center_point] = true;
k--;
while(k-- && !qu.empty()) {
P now = qu.top(); qu.pop();
int u = now.second;
for(int i=0;i<ve[u].size();i++) {
int v = ve[u][i].first;
if(!vis[v]) {
vis[v] = true;
qu.push(make_pair(dist_to_center[v], v));
}
}
}
if(qu.empty()) {
puts("0");
return ;
}
printf("%lld\n", qu.top().first);
}
int main() {
// freopen("1.in", "r", stdin);
int t; scanf("%d", &t);
while(t--) {
init();
if(n == 1) {
printf("0\n");
continue;
}
find_pointa_pointb();
find_center_point();
BFS();
}
return 0;
}
C题 Alice与Bob
预计10队可过
- 牛逼的人直接FFT,只要不手贱能过。
- 不牛逼的人将括号打开
∑
i
=
1
m
A
i
+
P
−
1
2
+
B
i
2
−
2
∗
A
i
+
P
−
1
∗
B
i
(
P
∈
(
1
,
n
−
m
+
1
)
)
\sum_{i=1}^{m} A_{i+P-1}^{2} + B_{i}^{2} - 2*A_{i+P-1}*B_{i} (P \in (1, n-m+1) )
i=1∑mAi+P−12+Bi2−2∗Ai+P−1∗Bi(P∈(1,n−m+1))
继续变化为
∑ i = 1 m A i + P − 1 2 , ( P ∈ ( 1 , n − m + 1 ) ) \sum_{i=1}^{m} A_{i+P-1}^{2}, (P \in (1, n-m+1) ) i=1∑mAi+P−12,(P∈(1,n−m+1))
∑ i = 1 m B i 2 , ( P ∈ ( 1 , n − m + 1 ) \sum_{i=1}^{m} B_{i}^{2} ,(P \in (1, n-m+1) i=1∑mBi2,(P∈(1,n−m+1)
∑ i = 1 m − 2 ∗ A i + P − 1 ∗ B i ( P ∈ ( 1 , n − m + 1 ) ) \sum_{i=1}^{m} - 2*A_{i+P-1}*B_{i} (P \in (1, n-m+1) ) i=1∑m−2∗Ai+P−1∗Bi(P∈(1,n−m+1))
这时候能发现Ai求和是一个连续长度为m的区间求和,从1开始每次后移一位,B数组直接求和n-m+1次,对于-2ab容易计算错误,可以先将-2×Bi提出来,然后计算次数。
FFT代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 100;
const int mod = 1e9 + 7;
typedef long long ll;
#define rep(i, a, n) for (int i=a;i<n;i++)
typedef long double db;
const int FFT_MAXN = 1 << 20;
const db pi = acos(-1.);
struct cp {
db a, b;
cp() {}
cp(db a, db b) : a(a), b(b) {}
cp operator+(const cp &y) const { return (cp) {a + y.a, b + y.b}; }
cp operator-(const cp &y) const { return (cp) {a - y.a, b - y.b}; }
cp operator*(const cp &y) const { return (cp) {a * y.a - b * y.b, a * y.b + b * y.a}; }
cp operator!() const { return (cp) {a, -b}; };
};
cp nw[FFT_MAXN + 1];
int bitrev[FFT_MAXN];
void dft(cp *a, int n, int flag = 1) {
int d = 0;
while ((1 << d) * n != FFT_MAXN)d++;
rep(i, 0, n)if (i < (bitrev[i] >> d))swap(a[i], a[bitrev[i] >> d]);
for (int l = 2; l <= n; l <<= 1) {
int del = FFT_MAXN / l * flag;
cp wn = {cos(flag*pi/(l>>1)),sin(flag*pi/(l>>1))};
for (int i = 0; i < n; i += l) {
cp *le = a + i, *ri = a + i + (l >> 1), *w = flag == 1 ? nw : nw + FFT_MAXN;
cp W = {1,0};
rep(k, 0, l >> 1) {
cp ne = *ri * W;
*ri = *le - ne, *le = *le + ne;
le++, ri++, w += del;
W = W*wn;
}
}
}
if (flag != 1)rep(i, 0, n)a[i].a /= n, a[i].b /= n;
}
void fft_init() {
int L = 0;
while ((1 << L) != FFT_MAXN)L++;
bitrev[0] = 0;
rep(i, 1, FFT_MAXN)bitrev[i] = bitrev[i >> 1] >> 1 | ((i & 1) << (L - 1));
// rep(i, 0, FFT_MAXN + 1)nw[i] = (cp) {cos(2 * pi / FFT_MAXN * i), sin(2 * pi / FFT_MAXN * i)}; //very slow
}
int X[maxn], Y[maxn];
ll sum1[maxn], sum2[maxn], c[FFT_MAXN];
void FS(int n, int m) {
ll ans = 0, s = 0;
for (int i = 1; i <= n; ++i) sum1[i] = sum1[i - 1] + 1ll * X[i] * X[i];
for (int i = 1; i <= m; ++i) s += 1ll * Y[i] * Y[i];
reverse(Y + 1, Y + m + 1);
static cp a[FFT_MAXN], b[FFT_MAXN];
int L = 1;
while (L < n + m - 1) L <<= 1;
for (int i = 0; i < L; ++i) {
if (i < n) a[i] = cp{X[i + 1], 0};
else a[i] = cp{0, 0};
if (i < m) b[i] = cp{Y[i + 1], 0};
else b[i] = cp{0, 0};
}
dft(a, L, 1), dft(b, L, 1);
for (int i = 0; i < L; ++i) a[i] = a[i] * b[i];
dft(a, L, -1);
for (int i = 0; i < L; ++i) {
c[i] = ll(a[i].a + 0.5);
}
for (int i = m - 1; i < n; ++i) ans -= 2 * c[i];
// cout<<ans<<'\n';
for (int i = 1; i <= n - m + 1; ++i) {
ans += (s + sum1[m + i - 1] - sum1[i - 1]);
}
cout << ans << '\n';
}
int main() {
// freopen("1.in", "r", stdin);
// freopen("1.out", "w", stdout);
ios::sync_with_stdio(0); cin.tie(0);
int T;
cin >> T;
fft_init();
while (T--) {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; ++i) cin >> X[i];
for (int i = 1; i <= m; ++i) cin >> Y[i];
FS(n, m);
}
return 0;
}
化公式代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e5+100;
typedef long long ll;
ll numa[maxn], numb[maxn], sum[maxn], pre_sum[maxn];//用了一下树状数组对每个点计数
int n, m;
void init() {
scanf("%d%d", &n, &m);
for(int i=1;i<=n;i++) {
scanf("%lld", &numa[i]);
pre_sum[i] = pre_sum[i-1] + numa[i];
}
for(int i=1;i<=m;i++)
scanf("%lld", &numb[i]);
for(int i=0;i<maxn;++i) sum[i] = 0;
}
int lowbit(int x) {
return x & -x;
}
void add(int pos, int va) {
while(pos <= n) {
sum[pos] += va;
pos += lowbit(pos)在这里插入代码片;
}
}
ll get_sum(int pos) {
ll Sum = 0;
while(pos > 0) {
Sum += sum[pos];
pos -= lowbit(pos);
}
return Sum;
}
void solve() {
for(int i=1;i<=n-m+1;i++) {
add(i, 1);
add(i+m, -1);
}
ll ans = 0;
ll Cnt = n - m + 1;
for(int i=1;i<=m;i++) ans += numb[i] * numb[i] * Cnt;
for(int i=1;i<=n;i++) ans += numa[i] * numa[i] * get_sum(i);
for(int i=1;i<=m;i++) ans -= 2 * numb[i] * (pre_sum[n-m+i] - pre_sum[i-1]);
printf("%lld\n", ans);
}
int main() {
// freopen("1.in", "r", stdin);
int t; scanf("%d",&t);
while(t--) {
init();
solve();
}
}
D题 超能量三角形
预计所有队过题
签到题,直接计数有多少个点在矩形内,设数量为Num
a
n
s
w
e
r
=
C
(
3
N
u
m
)
answer = C\binom{3}{Num}
answer=C(Num3)
#include <bits/stdc++.h>
using namespace std;
const int maxn = 110;
struct Node {
int x, y;
}node[maxn];
int n, q;
int main() {
//freopen("1.in", "r", stdin);
int t=1;
while(t--) {
scanf("%d%d",&n, &q);
for(int i=1;i<=n;i++) {
scanf("%d%d",&node[i].x, &node[i].y);
}
while(q--) {
int x1, y1, x2, y2;
scanf("%d%d%d%d",&x1, &y1, &x2, &y2);
int ans = 0;
for(int i=1;i<=n;i++) {
if(node[i].x >= x1 && node[i].x <= x2 && node[i].y >= y2 && node[i].y <= y1) ans++;
}
ans = ans * (ans-1) * (ans-2) / (3 * 2);
printf("%d\n", ans);
}
}
return 0;
}
E题 GPA计算
预计所有队伍过题
就是一个西科大的绩点计算公式,跟着公式走就行了。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 110;
int main() {
int n;
scanf("%d", &n);
double sum_score = 0, sum = 0;
for (int i = 1; i <= n; i++) {
double a, b, c;
scanf("%lf%lf", &a, &b);
c = 1 + (b-60)/10;
if(b < 60) c = 0;
sum += a;
a = a*c;
sum_score += a;
}
printf("%.3f\n", sum_score/sum);
return 0;
}
F题 乌龟与洞穴
预计20队过题
共三种解法
- 可以直接set维护 O(n * logn)复杂度内完成
- 不会stl可以用线段树维护复杂度同上,线段树维护最小值,从给定位置到n-1点找最小值,当找不到最小值的时候从线段树数起始位置开始找,找过的点重置为INT_MAX。
- 还可以使用并查集向右合并。
#include<bits/stdc++.h>
using namespace std;
int main() {
int T;
scanf("%d", &T);
while (T--) {
set<int> s;
int n;
scanf("%d", &n);
for (int i = 0; i < n; i++) {
s.insert(i);
}
while (n--) {
int x;
scanf("%d", &x);
auto it = s.lower_bound(x);
if (it == s.end()) {
it = s.begin();
}
printf("%d%c", *it, " \n"[n == 0]);
s.erase(it);
}
}
return 0;
}
G题 小Z的糖果难题
预计五个队过题
倍增法,Next[i][j]表示比第i个数大的第2的j次方个数的的位置,转移方程Next[i][j-1] = Next[Next[i][j-1]][j-1]。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+100;
const int bits = 20;
int Next[maxn][bits+5], que[maxn], n, q, num[maxn];
void init() {
scanf("%d%d",&n, &q);
for(int i=1;i<=n;i++) {
scanf("%d", &num[i]);
}
}
void get_Next0() {
//初始化第一个比它大的数的位置
int tail = 0;
for(int i=n;i>=1;i--) {
while(tail > 0 && num[que[tail]] <= num[i]) tail--;
que[++tail] = i;
if(tail >= 2) {
Next[i][0] = que[tail-1];
}
}
for(int i=n;i>=1;i--) {
for(int j=1;j<bits;j++) {
if(Next[i][j-1] == -1) break;
Next[i][j] = Next[Next[i][j-1]][j-1];
}
}
}
int query(int l, int r) {
if(l > r) return 0;
for(int i=bits-1;i>=0;i--) {
if(Next[l][i] <= r && Next[l][i] != -1) {
return (1<<i) + query(Next[l][i], r);
}
}
return 0;
}
int main() {
//freopen("1.in", "r", stdin);
memset(Next, -1, sizeof Next);
int t; scanf("%d", &t);
while(t--) {
init();
get_Next0();
while (q--) {
int l, r;
scanf("%d%d", &l, &r);
int ans = query(l, r);
printf("%d\n", ans+1);
}
for(int i=1;i<=n;i++)
for(int j=0;j<bits;j++)
Next[i][j] = -1;
}
return 0;
}
H题 进击的小说
预计0队做出
- 一本小说可能有三个状态
- 平稳态,在更新后没有更新的k2天里
- 上升态,连续更新了k1天之后继续更新
- 下降态,超过k2天没有更新
- 仔细观察发现其实上升态和平稳态都很好维护,只需要记录上次更新的天数和联系更新的天数,每次修改的时候直接O(1)修改就行了。但是下降态会比较繁琐,因为某本小说会在k2天无更新之后自动变成下降态。
- 下降态的特点是斜率相同,可以调节下降态的起始位置,根据斜率相同保持所有小说价值在下降态的单调性。这样就可以O(logn)的复杂度维护所有的下降态小说。
- 具体办法是给所有的下降态小说设计一个起始位置,然后放入set1中,set1中的所有下降态小说每一个回合减去同一个值不会改变单调性。设置一个set2放所有上升态和平稳态的小说,set1和set2都按照价值降序排列。如果有某本小说更新,这个小说在下降态集合中,将这个本小说取出,减去自定义设置的起始位置和当前位置差值,放入上升/平稳态集合set2中,如果该小说在上升态集合中直接取出更新再次放入就行。每次询问最大值,取上升/平稳set2集合的首个小说,如果该小说处于下降态,将该小说和设定的初始值结算之后转移到set1,然后在set2和set1中同时取首个元素的最大值就行了。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+100;
struct Fiction {
int update_time, va, con_days, belong;
bool operator <(const Fiction &x) const {
if(this->va != x.va)
return this->va > x.va;
else if(this->update_time != x.update_time)
return this->update_time > x.update_time;
else if(this->con_days != x.con_days)
return this->con_days > x.con_days;
else
return this->belong > x.belong;
}
}fic[maxn];
int n, k1, k2, v1, v2, d, down_va;//down_va是给下降态小说设定的一个初始值,保持内部单调性不变,可以看做讲一个下降斜率相同的直线平移到初始位置
set <Fiction> se, se_down;//平稳/上升态小说集合,下降态小说集合
void init() {
se.clear(); se_down.clear();
down_va = 0;
scanf("%d%d%d%d%d%d", &n, &k1, &k2, &v1, &v2, &d);
for(int i=1;i<=n;i++) {
scanf("%d", &fic[i].va);
fic[i].belong = i;
fic[i].update_time = 0;
fic[i].con_days = 0;
se.insert(fic[i]);
}
}
void deal(int x, int times) {
set <Fiction> :: iterator iter;
iter = se.find(fic[x]);
Fiction &now = fic[x];
if(iter == se.end()) {
iter = se_down.find(fic[x]);
now.va -= down_va - v2;
now.con_days = 1;
now.update_time = times;
se_down.erase(iter);
se.insert(fic[x]);
}
iter = se.find(fic[x]);
if(times - now.update_time > k2) {
now.va -= (times - now.update_time - k2) * v2;
}
if(now.update_time == times-1) now.con_days++;
else now.con_days = 1;
now.update_time = times;
if(now.con_days >= k1) {
now.va += v1;
}
se.erase(iter);
se.insert(now);
}
int get_max(int i) {
int Max1 = -1e9, Max2 = -1e9;
while(!se.empty()) {
Fiction now = *se.begin();
if(i - now.update_time >= k2) {
se.erase(se.begin());
now.va += down_va - (i - now.update_time - k2 + 1) * v2;
fic[now.belong] = now;
se_down.insert(now);
} else {
Max1 = now.va;
break;
}
}
if(!se_down.empty()) {
Fiction now = *se_down.begin();
Max2 = now.va - down_va ;
}
return max(Max1, Max2);
}
int main() {
// freopen("1.in", "r", stdin);
int t; scanf("%d", &t);
while(t--) {
init();
for(int i=1;i<=d;i++){
down_va += v2;
int cnt; scanf("%d", &cnt);
for(int j=1;j<=cnt;j++) {
int ci; scanf("%d", &ci);
//处理第ci本小说
deal(ci, i);
}
int ans = get_max(i);
printf("%d%c", ans, i==d? '\n':' ');
}
}
return 0;
}
I题 小C的二进制难题
预计15人过题
三种做法:
- 找规律递推
- 找规律打表
- 数位dp
数位dp代码:
#include<cstdio>
#include<cstring>
typedef long long LL;
const int maxn = 1e6 + 6;
LL dp[maxn];
const LL mod = 20190414;
int main()
{
dp[1] = 1;
LL a = 1;
for (int i = 1; i < maxn; i++) {
if(i-1) {
a<<=1;
}
a %= mod;
dp[i] = a + dp[i - 1] * 2;
dp[i] %= mod;
}
int T;
scanf("%d",&T);
while (T--)
{
int x;
scanf("%d",&x);
printf("%lld\n",dp[x] + 1);
}
return 0;
}
找规律递推
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 20190414;
vector <ll> sum;
void pre_deal(){
sum.push_back(0);
sum.push_back(1);
ll p = 1;
for(int i=2;i<=1e6;i++) {
p *= 2;
p %= mod;
ll temp = p + *sum.rbegin() + *sum.rbegin();
temp %= mod;
sum.push_back(temp);
}
}
int main() {
// freopen("1.in", "r", stdin);
pre_deal();
int t; scanf("%d", &t);
while(t--) {
int q;
scanf("%d", &q);
printf("%lld\n", (sum[q]+1)%mod);
}
return 0;
}
J题 异度空间
预计过题人数20
既然在圆里面不会影响路径长度,那就可以直接将一个圆看做一个类似质点的东西,没有大小和方向,然后将每一个点和其他点建立一条边,边长就是两个圆之间的距离,再跑一个最短路就行了。所有最短路的做法都能过。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1010;
struct Point {
double x, y, r;
}p[maxn];
int n, S, T;
double sx, sy, tx, ty;
double dis[maxn][maxn];
void init() {
scanf("%d", &n);
scanf("%lf%lf%lf%lf", &sx, &sy, &tx, &ty);
for(int i=1;i<=n;i++) {
scanf("%lf%lf%lf", &p[i].x, &p[i].y, &p[i].r);
}
p[n+1].x = sx, p[n+1].y = sy;
p[n+2].x = tx, p[n+2].y = ty;
}
double get_dis(int a, int b) {
double dis = sqrt((p[a].x - p[b].x)*(p[a].x - p[b].x) + (p[a].y - p[b].y)*(p[a].y - p[b].y));
return dis;
}
void build_maps() {
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
if(i == j) {
dis[i][j] = 0;
continue;
}
dis[i][j] = get_dis(i, j) - p[i].r - p[j].r;
}
}
}
void get_s_and_t() {
for(int i=1;i<=n;i++) {
if(get_dis(i, n+1) <= p[i].r) {
S = i;
}
if(get_dis(i, n+2) <= p[i].r) {
T = i;
}
}
}
bool vis[maxn];
double dist[maxn];
void spfa() {
for(int i=1;i<=n;i++) {
dist[i] = 1e10;
}
dist[S] = 0;
queue <int> qu;
qu.push(S);
vis[S] = true;
while(!qu.empty()) {
int now = qu.front();
qu.pop();
vis[now] = false;
for(int i=1;i<=n;i++) {
if(dist[i] > dist[now] + dis[now][i]) {
if(!vis[i]) {
vis[i] = true;
qu.push(i);
}
dist[i] = dist[now] + dis[now][i];
}
}
}
}
//void dij() {
// priority_queue <pair<double, int>, vector<pair<double, int> >, greater<pair<double, int> > > qu;
// for(int i=1;i<=n;i++) {
// dist[i] = 1e10;
// }
//
// dist[S] = 0;
// qu.push(make_pair(0, S));
//
// while(!qu.empty()) {
// pair<double, int> now = qu.top(); qu.pop();
//
// int u = now.second;
// for(int i=1;i<=n;i++) {
// if(dist[i] > dist[u] + dis[u][i]) {
// dist[i] = dist[u] + dis[u][i];
// qu.push(make_pair(dist[i], i));
// }
// }
// }
//}
int main() {
// freopen("1.in", "r", stdin);
int t;scanf("%d", &t);
while(t--) {
init();
build_maps();
get_s_and_t();
spfa();
// dij();
printf("%.9f\n", dist[T]);
}
return 0;
}
K题 小C的素数问题
预计0人过题
首先给出两个公式:
- W ( n ) = 2 k ( n ) = ∑ d ∣ n u 2 ( d ) W(n) = 2^{k(n)} = \sum_{d|n} u^2(d) W(n)=2k(n)=∑d∣nu2(d)
- ∑ i = 1 n u 2 ( i ) = ∑ i = 1 n u ( i ) ⋅ ⌊ n i 2 ⌋ \sum_{i=1}^n u^2(i)= \sum_{i=1}^{\sqrt{n}} u(i)\cdot\lfloor \frac{n}{i^2}\rfloor ∑i=1nu2(i)=∑i=1nu(i)⋅⌊i2n⌋
a n s = ∑ i = 1 n ∑ j = 1 n W ( g c d ( i , j ) ) ans =\sum_{i=1}^n \sum_{j=1}^n W(gcd(i,j)) ans=i=1∑nj=1∑nW(gcd(i,j))
莫比乌斯反演推出:
= ∑ d = 1 n W ( d ) ∑ d ∣ i ⌊ n i ⌋ 2 ⋅ u ( i d ) = \sum_{d=1}^nW(d) \sum_{d|i} \lfloor \frac{n}{i}\rfloor^2 \cdot u(\frac{i}{d}) =∑d=1nW(d)∑d∣i⌊in⌋2⋅u(di) 化简得:
= ∑ i = 1 n ⌊ n i ⌋ 2 ∑ d ∣ i W ( d ) ⋅ u ( i d ) = \sum_{i=1}^n \lfloor \frac{n}{i}\rfloor^2 \sum_{d|i} W(d)\cdot u(\frac{i}{d}) =∑i=1n⌊in⌋2∑d∣iW(d)⋅u(di) 。
对1式莫比乌斯反演得到: u 2 ( n ) = ∑ d ∣ n W ( d ) ⋅ u ( n d ) u^2(n) = \sum_{d|n}W(d)\cdot u(\frac {n}{d}) u2(n)=∑d∣nW(d)⋅u(dn)
那么 a n s = ∑ i = 1 n ⌊ n i ⌋ 2 ⋅ u 2 ( i ) ans = \sum_{i=1}^n \lfloor \frac{n}{i}\rfloor^2\cdot u^2(i) ans=∑i=1n⌊in⌋2⋅u2(i)
注意这个式子可以整数分块 ,问题转化为求快速求 S ( n ) = ∑ i = 1 n u 2 ( i ) S(n) = \sum_{i=1}^n u^2(i) S(n)=∑i=1nu2(i)
用式子2可以在 n \sqrt{n} n求出 S ( n ) S(n) S(n)
然后在努力优化一下就可通过此题。
时间复杂度 O ( n 3 4 ) O(n^{\frac{3}{4}}) O(n43)
有兴趣的下去研究
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int mod;
const int B = 2e7;
char u[B], pri[B];
int tab[B / 10], su[B], su2[B];
ll MOD(ll x) {
return x >= mod ? x -= mod : x;
}
void init(int n) {
u[1] = 1;
int cnt = 0;
for (int i = 2; i <= n; ++i) {
if (!pri[i]) u[i] = -1, tab[cnt++] = i;
for (int j = 0; j < cnt && tab[j] * i <= n; ++j) {
int &p = tab[j];
pri[p * i] = 1;
if (i % p == 0)break;
u[i * p] = -u[i];
}
}
for (int i = 1; i <= n; ++i) {
su[i] = su[i - 1] + u[i];
su2[i] = su2[i - 1] + u[i] * u[i];
}
}
int cnt = 0;
unordered_map<ll, ll> mp;
ll dfs2(ll n) { //return sum of u^2(i) (i<=n).
if (n < B) return su2[n];
if (mp.count(n)) return mp[n];
ll ret = 0;
for (ll i = 1, r; i * i <= n; i = r + 1) {
r = sqrt(n / (n / (i * i)) + 0.5);
ret += (su[r] - su[i - 1]) * (n / i / i);
}
return mp[n] = ret;
}
int main() {
// freopen("1.in", "r", stdin);
init(B - 1);
ll n, t;
cin >> t;
cnt = 0;
while (t--) {
cin >> n >> mod;
ll ret = 0;
for (ll i = 1, r; i <= n; i = r + 1) {
r = n / (n / i);
ret += (n / i) % mod * (n / i % mod) % mod * (dfs2(r) - dfs2(i - 1) + mod) % mod;
ret = MOD(ret);
}
if (n > 1e9) cnt++;
cout << ret << '\n';
}
return 0;
}
L题 我大哥
预计0人过题
不会做的人去看看LCT(Link Cut Tree), 看了就会了。
#include <bits/stdc++.h>
using namespace std;
const int MaxNode = 1e5 + 5;
int Lch[MaxNode], Rch[MaxNode], Pnt[MaxNode];
inline bool isRoot(int t) {
return !Pnt[t] || (Lch[Pnt[t]] != t && Rch[Pnt[t]] != t);
}
void LeftRotate(int cur) {
if (isRoot(cur)) return;
int pnt = Pnt[cur], anc = Pnt[pnt];
Lch[pnt] = Rch[cur];
if (Rch[cur]) Pnt[Rch[cur]] = pnt;
Rch[cur] = pnt;
Pnt[pnt] = cur;
Pnt[cur] = anc;
if (anc) {
if (Lch[anc] == pnt) Lch[anc] = cur;
else if (Rch[anc] == pnt) Rch[anc] = cur;
}
}
void RightRotate(int cur) {
if (isRoot(cur)) return;
int pnt = Pnt[cur], anc = Pnt[pnt];
Rch[pnt] = Lch[cur];
if (Lch[cur]) Pnt[Lch[cur]] = pnt;
Lch[cur] = pnt;
Pnt[pnt] = cur;
Pnt[cur] = anc;
if (anc) {
if (Lch[anc] == pnt) Lch[anc] = cur;
else if (Rch[anc] == pnt) Rch[anc] = cur;
}
}
void Splay(int cur) {
int pnt, anc;
while (!isRoot(cur)) {
pnt = Pnt[cur];
if (isRoot(pnt))
if (Lch[pnt] == cur) LeftRotate(cur);
else RightRotate(cur);
else {
anc = Pnt[pnt];
if (Lch[anc] == pnt)
if (Lch[pnt] == cur) LeftRotate(pnt), LeftRotate(cur);
else RightRotate(cur), LeftRotate(cur);
else if (Lch[pnt] == cur) LeftRotate(cur), RightRotate(cur);
else RightRotate(pnt), RightRotate(cur);
}
}
}
int Expose(int u) {
int v = 0;
for (; u; u = Pnt[u]) Splay(u), Rch[u] = v, v = u;
for (; Lch[v]; v = Lch[v]);
Splay(v);
return v;
}
void Join(int x, int y) {
int rx = Expose(x), ry = Expose(y);
if (rx == ry) {
puts("-1");
return;
} else {
Splay(x);
Rch[x] = 0;
Pnt[x] = y;
}
}
void Cut(int y) {
Expose(y);
Splay(y);
Pnt[Lch[y]] = 0;
Lch[y] = 0;
Pnt[y] = 0;
}
int main() {
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
double x = clock();
int n, m;
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
Pnt[i] = Lch[i] = Rch[i] = 0;
}
while (m--) {
char s[10];
int x, y;
scanf("%s %d %d", s, &x, &y);
if (s[0] == 'c') {
Cut(x);
Join(x, y);
} else {
int u = Expose(x), v = Expose(y);
if (u == v) {
puts("Yes");
} else {
puts("No");
}
}
}
// cout<<(clock()-x)/CLOCKS_PER_SEC<<'\n';
return 0;
}