西南科技大学第十三届程序设计竞赛题解

我两个队友出题,我验题,总的来说题还是十分简单的,比赛定位为娱乐性质的比赛,希望最后大家都能很快乐

总共十个可做的题,两个题应该没人提交。

最后两个题一个来自阳哥的热情贡献,是个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=1mAi+P12+Bi22Ai+P1Bi(P(1,nm+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=1mAi+P12(P(1,nm+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=1mBi2(P(1,nm+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=1m2Ai+P1Bi(P(1,nm+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人过题

首先给出两个公式:

  1. 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)=dnu2(d)
  2. ∑ 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=1n u(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=1nj=1nW(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)diin2u(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=1nin2diW(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)=dnW(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=1nin2u2(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;
}
  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值