2020牛客暑期多校训练营(第二场)

A. All with Pairs

#include 
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int mod = 998244353;
const ull base = 2333;
const int N = 1e6 + 10;
const int M = 1e5 + 10;
int n, m;

map<ull, int> mp;

//先将字符串的所有的后缀哈希存储 统计出现的次数
void getHash(string s) {
    ull x = 0, p = 1;
    int sz = s.length();
    for (int i = sz - 1; i >= 0; i--) {
        x += s[i] * p;
        p *= base;
        mp[x]++;
    }
}

int nxt[N];

// kmp的next数组 下标从0开始
void getNext(string s) {
    int sz = s.length();
    int k = -1;//当前前后缀能匹配出来的最大的长度 -1
    nxt[0] = k;
    for (int i = 1; i < sz; i++) {
        while (k > -1 && s[k + 1] != s[i]) k = nxt[k];
        if (s[k + 1] == s[i]) k++;//如果还能匹配上
        nxt[i] = k;
    }

}

string s[M];
int cnt[N];

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

    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> s[i];
        getHash(s[i]);
    }

    ll ans = 0;
    for (int i = 1; i <= n; i++) {
        int sz = s[i].length();
        ull x = 0;
        for (int j = 0; j < sz; j++) {
            x = x * base + s[i][j];
            cnt[j] = mp[x];//统计 前缀长度为j时 能与其相匹配的后缀的个数
        }
        getNext(s[i]);
        for (int j = 0; j < sz; j++) {
            if (nxt[j] > -1) {
                cnt[nxt[j]] -= cnt[j];
            }
            /*
             * 将重复统计的减掉 
             * as: 当 aba 与 aba 自身进行统计的时候
             * cnt[a]与 cnt[aba]都会被下面的for统计进去
             * 更优解当然是保留最大长度的aba 所以 需要从cnt[a]里 减去cnt[aba]的部分
             * 而通过nxt[j]可以很快的找到 在j前面 
             * 存在与前缀相匹配的后缀 且前缀部分从0到下标为nxt[j] 的 前缀部分最后那个位置
             * 
             * 前面提到 cnt[i]表示前缀部分为0~i位置 存在与之相匹配的后缀的个数
             * 所以写法是 cnt[nxt[j]]-=cnt[j]
             * */
        }

        for (int j = 0; j < sz; j++) {
            ans = (ans + ((1ll * cnt[j] * (j + 1) % mod) * (j + 1) % mod)) % mod;
            // 如果改成 ans+=... ans%=mod 这样会T
        }
    }
    cout << ans << endl;

    return 0;
}

B. Boundary

#include 
using namespace std;
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 Point {
    double x, y;

    Point() {}

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

    void input() { scanf("%lf%lf", &x, &y); }//输入

    void output() { printf("(%.2f %.2f)\n", 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); }

    Point operator*(const double &k) const { return Point(x * k, y * k); }

    Point operator/(const double &k) const { return Point(x / k, y / k); }

    bool operator==(Point b) const { return sgn(x - b.x) == 0 && sgn(y - b.y) == 0; }

    bool operator<(Point b) const { return sgn(x - b.x) == 0 ? sgn(y - b.y) < 0 : x < b.x; }


    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 distance(Point p) { return hypot(x - p.x, y - p.y); }
    //两点之间的距离


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

struct Line {
    Point s, e;

    Line() {}

    Line(Point _s, Point _e) {
        s = _s;
        e = _e;
    }

    bool operator==(Line v) { return (s == v.s) && (e == v.e); }

    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));
    }//求两直线的交点 前提:要保证直线不平行或重合

};

struct Circle {
    Point p;//圆心
    double r;//半径
    Circle() {}

    Circle(Point a, Point b, Point c) {
        Line u = Line((a + b) / 2, ((a + b) / 2) + ((b - a).rotLeft()));
        Line v = Line((b + c) / 2, ((b + c) / 2) + ((c - b).rotLeft()));
        p = u.cross_point(v);
        r = p.distance(a);
    }//三角形的外接圆

};


Point p[N];
map<Point, int> mp;
Circle cir;

int main() {

    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        p[i].input();
    }
    if (n <= 2) {
        printf("%d\n", n);
        return 0;
    }
    int res = 1;//肯定有一个点 在 与原点共圆
    //枚举两点+原点 所组成的三角形 产生外接圆
    for (int i = 1; i <= n; i++) {
        mp.clear();
        // 存在 p[i] p[j]形成的外接圆 与 p[j] p[k]所形成的外接圆是同一个的情况
        // 为防止p[j]重复统计 所以每次都需要清空map
        for (int j = i + 1; j <= n; j++) {
            if (sgn(p[i] ^ p[j]) == 0)continue;//两向量叉乘判断三点是否共线
            cir = Circle({0, 0}, p[i], p[j]);
            mp[cir.p]++;//统计p[j]
            res = max(res, mp[cir.p] + 1);//再加上p[i]
        }
    }
    printf("%d\n", res);


    return 0;
}

C. Cover the Tree

找底层叶子节点,根据叶子节点的排布分成左右两个部分,按照位置配对,让 v i v_{i} vi v m i d + i v_{mid+i} vmid+i配对

#include 
using namespace std;
const int N = 1e6 + 10;
int n, m, k;

vector<int> v;
vector<int> e[N];

void dfs(int u, int fa) {
    if (e[u].size() == 1) v.push_back(u);

    for (int i = 0, sz = e[u].size(); i < sz; i++) {
        int v = e[u][i];
        if (v != fa) {
            dfs(v, u);
        }
    }
}

int main() {

    scanf("%d", &n);
    for (int i = 1, u, v; i < n; i++) {
        scanf("%d%d", &u, &v);
        e[u].push_back(v);
        e[v].push_back(u);
    }
    dfs(1, 0);
    int sz = v.size();
    if (sz & 1) {
        v.push_back(v.back());
        sz++;
    }
    printf("%d\n", sz / 2);
    int mid = sz / 2;
    for (int i = 0; i < mid; i++) {
        printf("%d %d\n", v[i], v[mid + i]);
    }
    return 0;
}

D. Duration

#include 
using namespace std;
int a, b, c;

int main() {
    scanf("%2d:%2d:%2d", &a, &b, &c);
    int t1 = a * 3600 + b * 60 + c;

    scanf("%2d:%2d:%2d", &a, &b, &c);
    int t2 = a * 3600 + b * 60 + c;

    printf("%d\n", abs(t2 - t1));
    return 0;
}

E Exclusive OR


F. Fake Maxpooling

二维优先队列
有点卡时间

#include 
using namespace std;
#define N 5005
typedef long long ll;
int head1, tail1;
int qMax[N][N], matrix[N][N];

struct node {
    ll v;
    int id;
} q1[N];

void addMax(int v, int id) {  //维护队列最大值
    while (head1 < tail1 && q1[tail1 - 1].v <= v) tail1--;
    q1[tail1].v = v;
    q1[tail1++].id = id;
}

int getMaxNum(int id) {
    while (head1 < tail1 && q1[head1].id < id) head1++;
    return q1[head1].v;
}


int main() {
    int n, m, k, i, j;
    scanf("%d%d%d", &n, &m, &k);

    for (i = 1; i <= n; ++i)
        for (j = 1; j <= m; ++j)
            matrix[i][j] = i * j / __gcd(i, j);
    ll res = 0;
    for (j = 1; j <= m; ++j) {
        head1 = tail1 = 0;
        for (i = 1; i <= k - 1; ++i) {
            addMax(matrix[i][j], i);
        }
        for (i = k; i <= n; ++i) {
            addMax(matrix[i][j], i);
            qMax[i - k + 1][j] = getMaxNum(i - k + 1);
        }
    }

    for (i = 1; i <= n - k + 1; ++i) {
        head1 = tail1 = 0;
        for (j = 1; j <= k - 1; ++j) {
            addMax(qMax[i][j], j);

        }
        for (j = k; j <= m; ++j) {
            addMax(qMax[i][j], j);
            res += getMaxNum(j - k + 1);
        }
    }

    printf("%lld\n", res);
    return 0;
}

G Greater and Greater
H Happy Triangle
I Interval


J. Just Shuffle

终于看懂的一个博客 → 置换(群)&(J Just Shuffle)

假设原数组 a a a,经过置换 P P P,得到新的数组 c c c

  1. 置换公式 c i = P a i c_i=P_{a_i} ci=Pai
  2. a a a 经过 k k k 次置换 P P P 得到 c c c,用公式表示 a k = c a^k=c ak=c
  3. a a a 经过 k k k 次置换 P P P 得到 c c c,等同于 P P P 往后挪 k k k 位,形成新的置换 P ′ P' P ,再让 a a a 经过 1 1 1 次置换 P P P 得到 c c c

如果只是求 B ( 1 , 2 , 3.... n ) B(1,2,3....n) B(1,2,3....n) 经过一次置换 P P P 得到 A A A,是非常容易的,用公式表示
B 1 = A B^1=A B1=A

但实际上题目要求的是 B B B经过k次置换 P P P 得到 A A A,即
B k = A B^k=A Bk=A

这就需要借助置换 P P P 自身的变换,假设置换 P P P 往后挪了 i n v inv inv 次,根据前面的第3个知识点,上面的公式等同于
B k i n v = A B^{\frac{k}{inv}}=A Binvk=A

现在,只要让 k i n v = 1 \cfrac{k}{inv}=1 invk=1,就能根据得到的 P P P 往前挪 i n v inv inv 次得到原本的置换

而在模意义下,显然 i n v inv inv k k k 的逆元

同时,因为置换 P P P 的一组循环可能无法包含所有的位置,需要分别求出每一组循环的置换 P ′ P' P 以及对应的 i n v inv inv,最后结合起来才是答案

数据水了所以没有无解?

据说是原题来着…ICPC NEAU Programming Contest 2020 E. 随便置换(模拟枚举环,置换)

#include 

using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
int a[N];
int n, k;
int res[N];

int vis[N];
vector<int> p;

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


    cin >> n >> k;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }

    for (int i = 1; i <= n; i++) {
        if (!vis[i]) {
            int now = i;
            p.clear();
            //找到每一组循环里的所有位置
            while (!vis[now]) {
                p.push_back(now);
                vis[now] = 1;
                now = a[now];// 置换公式代入 a[i]=p[b[i]] ∵b[i]=i ∴ a[i]=p[i]
            }

            int sz = p.size();
            int inv = 0;// 如果用费马小定理求逆元 可能存在模数为负数的情况
            while (inv < sz) {
                if ((ll) inv * k % sz == 1) break; // 找到一种inv即可
                inv++;
            }

            for (int i = 0; i < sz; i++) {
                res[p[i]] = p[(i + inv) % sz]; // 将原来的序列挪位
            }
        }
    }
    for (int i = 1; i <= n; i++) {
        cout << res[i] << " ";
    }

    return 0;
}

K Keyboard Free

你可能感兴趣的:(多校)