2020 Multi-University Training Contest 1

Avian Darts
Boring Task
Cookies

Distinct Sub-palindromes

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const ll mod = 998244353;

ll qpow(ll a, ll b) {
    a %= mod;
    ll res = 1;
    while (b) {
        if (b & 1)res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}

ll n;
ll Cn3 = (24 * 25 * 26) % mod;// C(n,3)

int main() {
    
    int T;
    cin >> T;
    while (T--) {
        cin >> n;
        if (n <= 3)
            cout << qpow(26, n) << endl;
        else
            cout << Cn3 << endl;
    }
    return 0;
}

Fibonacci Sum

赛中交的代码赛后疯狂T,感谢测评姬放过……

已知斐波那契有如下:
F ( n ) = 1 5 [ ( 1 + 5 2 ) n − ( 1 − 5 2 ) n ] F(n)=\frac{1}{\sqrt 5}\bigg[ \bigg( \frac{1+\sqrt 5}{2} \bigg)^n- \bigg( \frac{1-\sqrt 5}{2} \bigg)^n \bigg] F(n)=5 1[(21+5 )n(215 )n]
再扩展一下每一项的k次幂
F ( n ) k = ( 1 5 ) k [ ( 1 + 5 2 ) n − ( 1 − 5 2 ) n ] k F(n)^k=\bigg( \frac{1}{\sqrt 5} \bigg)^k\bigg[ \bigg( \frac{1+\sqrt 5}{2} \bigg)^n- \bigg( \frac{1-\sqrt 5}{2} \bigg)^n \bigg]^k F(n)k=(5 1)k[(21+5 )n(215 )n]k
其中,令 a = ( 1 + 5 2 ) n a=\bigg( \frac{1+\sqrt 5}{2} \bigg)^n a=(21+5 )n
b = ( 1 − 5 2 ) n b=\bigg( \frac{1-\sqrt 5}{2} \bigg)^n b=(215 )n
则有
[ ( 1 + 5 2 ) n − ( 1 − 5 2 ) n ] k = ( a n − b n ) k \bigg[ \bigg( \frac{1+\sqrt 5}{2} \bigg)^n- \bigg( \frac{1-\sqrt 5}{2} \bigg)^n \bigg]^k=(a^n-b^n)^k [(21+5 )n(215 )n]k=(anbn)k
注意到,这是一个二项式

( a n − b n ) k = C k 0 ( a n ) 0 ( − b n ) k + C k 1 ( a n ) 1 ( − b n ) k − 1 + . . . . . + C k k ( a n ) k ( − b n ) 0 = ∑ i = 0 k ( − 1 ) k − i ( a n ) i ( b n ) k − i (a^n-b^n)^k=C^0_k(a^n)^0(-b^n)^k+C^1_k(a^n)^1(-b^n)^{k-1}+.....+C^k_k(a^n)^k(-b^n)^0\\=\sum^k_{i=0}(-1)^{k-i}(a^n)^i(b^n)^{k-i} (anbn)k=Ck0(an)0(bn)k+Ck1(an)1(bn)k1+.....+Ckk(an)k(bn)0=i=0k(1)ki(an)i(bn)ki
注意一下,这里把 − b n -b^n bn 里的负号提取出来了,用处在后面会提到

因此,
F ( n ) k = ( 1 5 ) k [ ∑ i = 0 k ( − 1 ) k − i C k i ( a n ) i ( b n ) k − i ] F(n)^k=\bigg( \frac{1}{\sqrt 5} \bigg)^k\bigg[\sum^k_{i=0}(-1)^{k-i}C^i_k(a^n)^i(b^n)^{k-i}\bigg] F(n)k=(5 1)k[i=0k(1)kiCki(an)i(bn)ki]

结合题目给出的n、c、k,用c替换上式的n

F ( c ) k = ( 1 5 ) k [ ∑ i = 0 k ( − 1 ) k − i C k i ( a c ) i ( b c ) k − i ] F(c)^k=\bigg( \frac{1}{\sqrt 5} \bigg)^k\bigg[\sum^k_{i=0}(-1)^{k-i}C^i_k(a^c)^i(b^c)^{k-i}\bigg] F(c)k=(5 1)k[i=0k(1)kiCki(ac)i(bc)ki]

然后再看看F(2c)时的式子
F ( 2 c ) k = ( 1 5 ) k [ ( 1 + 5 2 ) 2 c − ( 1 − 5 2 ) 2 c ] k = ( 1 5 ) k [ ∑ i = 0 k ( − 1 ) k − i C k i ( a 2 c ) i ( b 2 c ) k − i ] = ( 1 5 ) k [ ∑ i = 0 k ( − 1 ) k − i C k i ( ( a c ) i ( b c ) k − i ) 2 ] F(2c)^k=\bigg( \frac{1}{\sqrt 5} \bigg)^k\bigg[ \bigg( \frac{1+\sqrt 5}{2} \bigg)^{2c}- \bigg( \frac{1-\sqrt 5}{2} \bigg)^{2c} \bigg]^k\\=\bigg( \frac{1}{\sqrt 5} \bigg)^k\bigg[\sum^k_{i=0}(-1)^{k-i}C^i_k(a^{2c})^i(b^{2c})^{k-i}\bigg]\\=\bigg( \frac{1}{\sqrt 5} \bigg)^k\bigg[\sum^k_{i=0}(-1)^{k-i}C^i_k\bigg((a^{c})^i(b^{c})^{k-i}\bigg)^2\bigg] F(2c)k=(5 1)k[(21+5 )2c(215 )2c]k=(5 1)k[i=0k(1)kiCki(a2c)i(b2c)ki]=(5 1)k[i=0k(1)kiCki((ac)i(bc)ki)2]

可以发现 F ( c ) F(c) F(c) F ( 2 c ) F(2c) F(2c) 之间,二项式里每一项的 ( a c ) i ( b c ) k − i (a^{c})^i(b^{c})^{k-i} (ac)i(bc)ki 翻倍了
由此推出 F ( n c ) F(nc) F(nc) 时的式子
F ( n c ) k = ( 1 5 ) k [ ∑ i = 0 k ( − 1 ) k − i C k i ( ( a c ) i ( b c ) k − i ) n ] F(nc)^k=\bigg( \frac{1}{\sqrt 5} \bigg)^k\bigg[\sum^k_{i=0}(-1)^{k-i}C^i_k\bigg((a^{c})^i(b^{c})^{k-i}\bigg)^n\bigg] F(nc)k=(5 1)k[i=0k(1)kiCki((ac)i(bc)ki)n]
对于各个 F ( x c ) x = 1... n F(xc)_{x=1...n} F(xc)x=1...n的二项式里第 i i i 项,存在公比 q = ( a c ) i ( b c ) k − i q=(a^{c})^i(b^{c})^{k-i} q=(ac)i(bc)ki,可以等比公式求和统计

S ( n ) = a 1 × q n − 1 q − 1 S(n)=a_1\times\frac{q^n-1}{q-1} S(n)=a1×q1qn1

前面提取的负号,也是为了方便后面的求和统计

重点:

模数 p = 1 0 9 + 9 p=10^9+9 p=109+9 是一个质数

1 5 \cfrac{1}{\sqrt 5} 5 1 在模意义下需要借助 二次剩余 来求,即 x 2 = 5 m o d    1 0 9 + 9 x^2=5\mod {10^9+9} x2=5mod109+9

得到x的一个解: x = 5 = 383008016 x=\sqrt 5=383008016 x=5 =383008016

再结合费马小定理求出斐波那契公式里的 a a a b b b

a = 1 + 5 2 = 691504013 b = 1 − 5 2 = 308495997 1 5 = 276601605 a= \frac{1+\sqrt 5}{2} =691504013\\b= \frac{1-\sqrt 5}{2} =308495997\\\frac{1}{\sqrt 5}=276601605 a=21+5 =691504013b=215 =3084959975 1=276601605

其次 n , c ≤ 1 0 18 n,c\le10^{18} n,c1018 数据过大,需要 欧拉降幂
公式如下:
a b m o d    p = a b m o d    φ ( p ) + φ ( p ) m o d    p a^b \mod p=a^{b \mod \varphi(p)+\varphi(p)}\mod p abmodp=abmodφ(p)+φ(p)modp

并且由于 1 0 9 + 9 10^9+9 109+9是个质数,可以根据欧拉函数的性质直接得出phi值就是 模数本身-1

φ ( 1 0 9 + 9 ) = 1 0 9 + 8 \varphi(10^9+9)=10^9+8 φ(109+9)=109+8

ps:如果还T的话,scanf改cin、关掉一两个数组,你就可以过去了……

// 赛后ac代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 9;
const int N = 1e5 + 10;

ll qpow(ll a, ll b) {
    a %= mod;
    ll res = 1;
    while (b) {
        if (b & 1)res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}

ll Phi = mod - 1;//欧拉降幂

ll euler(ll a, ll b) {
    return qpow(a, ((b % Phi) + Phi));
}

ll fac[N], inv[N];
void init(int n) {
    fac[0] = inv[0] = 1;
    for (int i = 1; i <= n; ++i) {
        fac[i] = fac[i - 1] * i % mod;
        inv[i] = inv[i - 1] * qpow(i, mod - 2) % mod;
    }
}

ll C(int n, int m) {
    if (n < m || m < 0)return 0;
    return fac[n] * inv[m] % mod * inv[n - m] % mod;
}

ll Add(ll x, ll y) {
    if (x < 0)x += mod;
    if (y < 0) y += mod;
    return (x + y) % mod;
}

ll Mul(ll x, ll y) {
    x %= mod;
    y %= mod;
    return x * y % mod;
}

ll n, c;
int k;
ll a = 691504013;// (1+sqrt(5))/2
ll b = 308495997;// (1-sqrt(5))/2
ll Inv5 = 276601605;// 1/sqrt(5)

ll A, B, A_B, q;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    init(100000);

    int T;
    cin >> T;
    while (T--) {
        cin >> n >> c >> k;
        A = qpow(a, c);// a^c
        B = qpow(b, c);// b^c
        A_B = Mul(A, qpow(B, mod - 2));// A/B
        q = qpow(B, k);//公比

        ll res = 0, tmp;
        for (int i = 0; i <= k; i++) {
            //if (q == 0)continue; mod为质数 永远不会有公比为0的时候
            if (q == 1) {
                tmp = n % mod;
            } else {
                tmp = Mul(Mul(Add(euler(q, n), mod - 1), qpow(Add(q, mod - 1), mod - 2)), q);
            }
            tmp = Mul((C(k, i) * ((k - i) & 1 ? -1 : 1)), tmp);
            res = Add(res, tmp);
            q = Mul(q, A_B);
        }
        res = Mul(res, qpow(Inv5, k));
        cout << res << endl;
        
    }
    return 0;
}

Finding a MEX

#include <bits/stdc++.h>

using namespace std;
const int N = 1e5 + 3;

vector<int> e[N], E[N];
int vis[N];
int a[N];
int u[N], v[N], d[N];
int n, m;

struct segTree {
#define ls (o<<1)
#define rs (o<<1|1)

    struct node {
        int l, r;
        int sum;
    } t[N << 2];

    int cnt[N];

    void pushup(int o) {
        t[o].sum = t[ls].sum + t[rs].sum;
    }

    //不带数组 建树
    void build(int o, int l, int r) {
        t[o].l = l;
        t[o].r = r;
        t[o].sum = 0;
        if (l == r) {
            cnt[l] = 0;
            return;
        }
        int mid = l + r >> 1;
        build(ls, l, mid);
        build(rs, mid + 1, r);
    }

    int query(int o) {
        if (t[o].l == t[o].r) {
            return t[o].l;
        }
        int len = t[o].r - t[o].l + 1;
        if (t[ls].sum >= (len - (len >> 1)))
            return query(rs);
        else return query(ls);
    }

    void add(int o, int pos, int val) {
        if (t[o].l == t[o].r) {
            cnt[t[o].l] += val;

            if (cnt[t[o].l] > 0)
                t[o].sum = 1;
            else
                t[o].sum = 0;
            return;
        }
        int mid = t[o].l + t[o].r >> 1;
        if (pos <= mid) add(ls, pos, val);
        else add(rs, pos, val);
        pushup(o);
    }

} ST[351];

int id[N], dfn = 0;
int Hash[N];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int T;
    cin >> T;
    while (T--) {
        cin >> n >> m;

        //init
        dfn = 0;
        for (int i = 1; i <= n; i++) {
            e[i].clear();//所有边
            E[i].clear();//大点连边
            vis[i] = d[i] = 0;
            id[i] = 0;
        }

        for (int i = 1; i <= n; i++) {
            cin >> a[i];
            a[i]++;//在原来的基础上+1 便于线段树维护
        }

        for (int i = 1; i <= m; i++) {
            cin >> u[i] >> v[i];
            e[u[i]].push_back(v[i]);
            e[v[i]].push_back(u[i]);

            d[u[i]]++;
            d[v[i]]++;
        }

        int block = 1000;

        for (int i = 1; i <= n; i++) {
        	// 区分 部分度数超过block的点为大点 没有超过的为小点
            if (d[i] > block) {
                vis[i] = 1; // 标记大点
            }
        }

        for (int i = 1; i <= m; i++) {
            if (vis[v[i]]) // 当v[i]是大点时 需要对v[i]建线段树 如果u[i]要修改权值 则v[i]对应的线段树也要修改
                E[u[i]].push_back(v[i]);
            
            if (vis[u[i]]) 
                E[v[i]].push_back(u[i]);
        }

        for (int i = 1; i <= n; i++) {
            if (vis[i]) {
                id[i] = ++dfn; // 记录点i对应的是哪颗线段树
                ST[dfn].build(1, 1, d[i] + 1);
                for (int j : e[i]) {
                    if (a[j] <= d[i])
                        ST[dfn].add(1, a[j], 1);
                }
            }
        }

        int q, op, x, y;
        cin >> q;
        while (q--) {
            cin >> op;
            if (op == 1) {
                //将 a[x]修改成y
                cin >> x >> y;
                y++;
                for (int z:E[x]) {
                    if (vis[z]) { // 如果相连的是大点
                        if (a[x] <= d[z])
                            ST[id[z]].add(1, a[x], -1);
                        if (y <= d[z]) // 如果y值比点z的度数还大 显然y不可能是F(z)的答案
                            ST[id[z]].add(1, y, 1);
                    }
                }
                a[x] = y;

            } else { //op=2
                cin >> x;
                if (vis[x]) {
                    cout << ST[id[x]].query(1) - 1 << endl;
                } else {
                    // 用set会t
                    for (int i = 1; i <= d[x]; i++) 
                        Hash[i] = 0;
                    
                    for (int z:e[x]) {
                        if (a[z] > d[x])continue;
                        Hash[a[z]] = 1;
                    }
                    int res = 1;
                    for (int i = 1; i <= d[x]; i++) 
                        if (!Hash[i]) {
                            res = i;
                            break;
                        }
                    
                    cout << res - 1 << endl;
                }
            }
        }
    }

    return 0;
}

Hunting Monsters
Integral Calculus

Leading Robots

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const double eps = 1e-8;
const int N = 1e6 + 10;
int n;

int sgn(double x) {
    if (fabs(x) < eps) return 0;
    if (x < 0) return -1;
    else return 1;
}
//判断小数和0是否等于

struct Line {
    // y=kx+b
    ll k, b;

    bool operator==(const Line line) const {
        return k == line.k && b == line.b;
    }

    bool operator<(const Line line) const {
        if (b == line.b) {
            return k < line.k;
        }
        return b > line.b;
    }
} L[N];

int Stack[N], top = 0;

int check(Line l1, Line l2, Line l3) {
    double x1 = (double) (l2.b - l1.b) / (double) (l1.k - l2.k); // 第一和第二的交点
    double x2 = (double) (l3.b - l2.b) / (double) (l2.k - l3.k); // 新的直线和第二的交点
    if (sgn(x1 - x2) > 0) {
        return 1;
    } else if (sgn(x1 - x2) == 0) {
        return 2;
    }
    return 3;
    // 1 (x1,y1)在 (x2,y2)的左边
    // 2 (x1,y1)和(x2,y2)是同一个点
    // 3 (x1,y1)在 (x2,y2)的右边
}

int vis[N];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    
    int T;
    ll p, a;
    cin >> T;
    while (T--) {
        // init
        top = 0;

        cin >> n;

        // y=1/2at^2+p -> 2y=a(t^2)+2p
        for (int i = 1; i <= n; i++) {
            cin >> p >> a;
            
            L[i].k = a;
            L[i].b = p * 2;

            // init
            vis[i] = 0; // 用于判断是否重线
        }

        sort(L + 1, L + 1 + n);

        for (int i = 1, first, second, x; i <= n; i++) {
            if (top && L[first = Stack[top]].k >= L[i].k) { // L[i]永远追不上第一
                if (L[first] == L[i]) vis[first] = 1;//同一条直线 第一也不能要
                continue;
            }
            if (top < 2) {
                // 但是也存在后面的点和第一是同一个起点 加速度比第一大 即一开始大家都是第一 后一秒L[i]超过第一
                if (top == 1 && L[first].b == L[i].b) {
                    Stack[top] = i;
                } else
                    Stack[++top] = i;
            } else {//top>=2
                // 如果第一与第二的交点 在 第二与L[i]的右边 说明L[i]比第一先成领跑
                while (top > 1 && check(L[first], L[second = Stack[top - 1]], L[i]) != 3) {
                    first = second;
                    top--;//踢掉第一
                }
                Stack[++top] = i;
            }
        }
        int res = 0;
        for (int i = 1; i <= top; i++) {
            if (!vis[Stack[i]]) {
                res++;
            }
        }
        cout << res << endl;
    }
    return 0;
}

Math is Simple
Minimum Index

Mow

手工除草每平米 A A A 元,半径为 r r r 圆形除草除草每平米 B B B 元,问一个多边形草地的最小除草花费

除草机能工作到的面积 = 多边形内核往里缩 r r r 米的新内核的面积 + 新内核的周长 × r \times r ×r + r 2 π r^2\pi r2π

借个别人的图 - 图片出处
在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, m, k;
const double eps = 1e-8;
const double pi = acos(-1.0);

int sgn(double x) {
    if (fabs(x) < eps) return 0;
    if (x < 0) return -1;
    else return 1;
}
//判断小数和0是否等于

// ------------ 点、向量 ------------
struct Point {
    double x, y;

    Point() {}

    Point(double _x, double _y) { x = _x, y = _y; }

    void input() {
        cin >> x >> y;
    }//输入
    
    Point operator-(const Point &b) const { return Point(x - b.x, y - b.y); }
    Point operator+(const Point &b) const { return Point(x + b.x, y + b.y); }
    double operator^(const Point &b) const { return x * b.y - y * b.x; }
    //叉积
    double operator*(const Point &b) const { return x * b.x + y * b.y; }
    //点积

    double len() { return hypot(x, y); }
    //返回长度
    
    double distance(Point p) { return hypot(x - p.x, y - p.y); }
    //两点之间的距离

    Point trunc(double r) {
        double l = len();
        if (!sgn(l)) return *this;
        r /= l;
        return Point(x * r, y * r);
    }//化为长度为r的向量

    Point rotLeft() { return Point(-y, x); }
    //逆时针旋转90°

    Point rotRight() { return Point(y, -x); }
    //顺时针旋转90°
    
    Point normal(const bool rsh = false) {
        return rsh ? rotLeft() : rotRight();
    }// 法向量
};
typedef Point Vector;

// ------------ 直线 ------------
struct Line {
    Point s, e;
    Vector v;
    double ang;

    Line() {}

    Line(Point s1, Point e1) : s(s1), e(e1) {
        v = e1 - s1;
        ang = atan2(v.y, v.x);
    }

    bool operator<(const Line &L) const {
        return ang < L.ang;
    }//用于极角排序
    
    int relation(Point p) {
        int c = sgn((p - s) ^ (e - s));
        if (c < 0) return 1; //点在直线的左侧
        else if (c > 0) return 2;//点在直线的右侧
        else return 3;//点在直线上
    }//判断点与直线的关系
    
    Point cross_point(Line v) {
        double a1 = (v.e - v.s) ^(s - v.s);
        double a2 = (v.e - v.s) ^(e - v.s);
        return Point((s.x * a2 - e.x * a1) / (a2 - a1), (s.y * a2 - e.y * a1) / (a2 - a1));
    }//求两直线的交点 前提:要保证直线不平行或重合
    
    bool onLeft(Point p) {
        return relation(p) == 1;
    }
};

namespace Polygon { //多边形板子
    // 求多边形的面积
    // 顺时针还是逆时针都可 最终都会搞成逆时针
    double polygon_area(vector<Point> &p) {
        const int n = p.size();
        double res = 0;
        for (int i = 0; i < n; i++) {
            res += p[i] ^ p[(i + 1) % n];
        }
        res /= 2;
        if (sgn(res) < 0) {
            reverse(p.begin(), p.end());
            res *= -1;
        }
        return res;
    }

    // 半平面交求内核
    vector<Point> HPI(vector<Line> L) {
        const int n = L.size();
        sort(L.begin(), L.end());
        int first, last;//指向双端队列首尾元素
        vector<Point> p(n);//两个相邻半平面的焦点
        vector<Line> q(n);//模拟双端队列
        vector<Point> res;//半平面交形成的凸包

        q[first = last = 0] = L[0];
        for (int i = 1; i < n; i++) {
            //情况1 L[i]覆盖原队尾:删除尾元素 的半平面
            while (first < last && !L[i].onLeft(p[last - 1])) last--;
            //情况2 L[i]覆盖原队首:删除首元素的半平面
            while (first < last && !L[i].onLeft(p[first])) first++;
            q[++last] = L[i];//将当前的半平面加入双端队列的队尾
            //极角相同的两个半平面保留左边的那个
            if (sgn(q[last].v ^ q[last - 1].v) == 0) {//根据直线代表的向量判断是否平行
                last--;
                if (q[last].onLeft(L[i].s)) q[last] = L[i];
            }
            //计算队尾无用的半平面
            if (first < last) p[last - 1] = q[last - 1].cross_point(q[last]);
        }
        //情况3 L[i]不能加入到队列: 删除队列首尾无用的半平面
        while (first < last && !q[first].onLeft(p[last - 1])) last--;
        if (last - first <= 1) return res;//空集
        p[last] = q[last].cross_point(q[first]);//计算队列首尾的交点
        return res = vector<Point>(p.begin() + first, p.begin() + last + 1);
    }
}
using namespace Polygon;

ll r;//半径
ll A, B;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int T;
    cin >> T;
    for (int cs = 1; cs <= T; cs++) {
        cin >> n >> r >> A >> B;
        vector<Point> p(n);
        for (int i = 0; i < n; i++) {
            p[i].input();
        }
        //全手工费用
        double s1 = polygon_area(p);
        double res1 = s1 * A;
        vector<Line> L;
        for (int i = 0; i < n; i++) {
            Vector nml = (p[(i + 1) % n] - p[i]).normal(1).trunc(r);//长度为r的法向量
            L.push_back({p[i] + nml, p[(i + 1) % n] + nml});// 直线向内平移r个单位 <- 重点
        }

        vector<Point> core = HPI(L);
        if (!core.empty()) {
            m = core.size();
            double c = 0;//周长
            for (int i = 0; i < m; i++) {
                c += core[i].distance(core[(i + 1) % m]);
            }
            // 新的面积 = 内核面积 + 内核周长*r + 一个圆
            double s2 = polygon_area(core) + c * r + pi * r * r;
            res1 = min(res1, (s1 - s2) * A + s2 * B);
        }
        // 输出20位数字 cout写法
        cout << fixed << setprecision(20)  << res1 << endl;
    }
    return 0;
}
©️2020 CSDN 皮肤主题: 像素格子 设计师:CSDN官方博客 返回首页