Codeforces Round 395 div1

Codeforces Round 395 div1

contest链接

tags

同余方程费马小定理树hash换根线段树并查集

码量细节:E>C>D>A>B

难度:C>E>D>B>A

A. Timofey and a tree

题意:给一棵点染色树,求一个根节点,使得除了原树以外所有的子树节点颜色都一样。

题解:首先考虑直接维护颜色相同的联通块,处理出所有颜色一样的块后如果块形成的树直径不超过3就有机会找到答案,但是由于3个块中间的宽度可能不是1,导致找不到一个根节点好像很不好处理。考虑直接维护颜色分界的边。如果有解,那么分界边一定会共享同一个节点,问题变成n个pair,求是否存在一个x在这n个pair中都出现过,就可以线性求解了。如果不存在分界边则任意选一个节点都满足条件。

代码:

/*================================================================
*
*   创 建 者: badcw
*   创建日期: 2020/5/11
*
================================================================*/
#include <bits/stdc++.h>

#define ll long long
using namespace std;

const int maxn = 1e5+50;
int main(int argc, char* argv[]) {
    int n;
    scanf("%d", &n);
    vector<pair<int, int> > a(n - 1);
    for (int i = 0; i < n - 1; ++i) scanf("%d%d", &a[i].first, &a[i].second);
    vector<int> c(n + 1);
    for (int i = 1; i <= n; ++i) scanf("%d", &c[i]);
    vector<pair<int, int> > x;
    for (auto i : a) {
        if (c[i.first] != c[i.second]) {
            x.emplace_back(i);
        }
    }
    if (x.empty()) printf("YES\n1\n");
    else {
        int flag = 1;
        for (auto i : x) {
            if (i.first == x[0].first || i.second == x[0].first) {
                continue;
            }
            flag = 0;
            break;
        }
        if (flag) printf("YES\n%d\n", x[0].first);
        else {
            flag = 1;
            for (auto i : x) {
                if (i.first == x[0].second || i.second == x[0].second) continue;
                flag = 0;
                break;
            }
            if (flag) printf("YES\n%d\n", x[0].second);
            else printf("NO\n");
        }
    }
    return 0;
}

B. Timofey and rectangles

题意:给一些矩形的位置,求是否能用四种颜色染色使得没有紧靠的矩形同色,求方案。额外条件:没有矩形相交或相包含,所有矩形的边长都为奇数。

题解:边长为奇数可以得出四种情况,分别是行和列上的:1、下奇上偶,2、下偶上奇,3、左奇右偶,4、左偶右奇,其中(1,2),(3,4)可以两两相邻,所以直接对某个边界点encoding即可,一定存在合法解。

代码:

/*================================================================
*
*   创 建 者: badcw
*   创建日期: 2020/5/11
*
================================================================*/
#include <bits/stdc++.h>

#define ll long long
using namespace std;

const int maxn = 5e5+50;
int main(int argc, char* argv[]) {
    int n;
    scanf("%d", &n);
    printf("YES\n");
    for (int i = 0; i < n; ++i) {
        int xl, yl, xr, yr;
        scanf("%d%d%d%d", &xl, &yl, &xr, &yr);
        int t1 = (yl % 2 + 2) % 2;
        int t2 = (xl % 2 + 2) % 2;
        printf("%d\n", t2 * 2 + t1 + 1);
    }
    return 0;
}

C. Timofey and remoduling

题意:在模m(素数)意义下,求长度为n的序列\(a_i=a_{i-1}+d\),等于题目给出的n个数字的一种排列。

题解:首先\(a_i=a_{i-1}+d\)在模m是素数意义下的循环节为m,也就是说在1~n中,\(a_i\)都是distinct的,然后考虑一个问题\(a_i+d*k=a_j\)这个式子什么时候会有j的唯一解,显然是一个循环节内的情况。考虑当n*2<m时,对于\(a_i+n*d\),有\(i+n<m\),那么也就是说对于任意的\(a_j-a_i=(j-i)d\)\(j-i\)是不超过\(n\)的,也就是说\(a_j+d*k=a_j\)有唯一解\(j\),当然反之若n*2>=m时,就可能出现溢出一个循环节的情况。那么对于n*2<m我们可以枚举任意一组\((i,j)\),对于他们找出所有可能的\(a_x+(j-i)*d=a_y\)个数,那么可以说一共有\(n-(j-i)+1\)组,枚举解出组数之后也就知道了\((i,j)\)之间的距离,同时也知道\(a_j\)\(a_i\),所以就可以求出\(d\),那么对于任意的\(a_i\),求出\(a_0\)也就只需要不断的\(-d\)即可,解出\(a_0\)\(d\)就可以\(O(nlogn)\)的check是否满足题意。对于n*2>=m则需要求出补集,也就是后半段循环,这个循环与题目要求的循环的差别只在于\(a_0\)的不同,也就是存在一个\(n*d\)的偏移量,就可以很方便的解出答案。

代码:

/*================================================================
*
*   创 建 者: badcw
*   创建日期: 2020/5/11
*
================================================================*/
#include <bits/stdc++.h>

#define ll long long
using namespace std;

const int maxn = 1e5+50;
const int mod = 1e9+7;
ll qp(ll a, ll n, ll mod = ::mod) {
    ll res = 1;
    while (n > 0) {
        if (n & 1) res = res * a % mod;
        a = a * a % mod;
        n >>= 1;
    }
    return res;
}

int n, m;
int x, d;


// 1 2 3 4 5
// k = 3
// 1 4, 2 5 tmp = 2

void solve(const vector<int>& a) {
    if (a.size() == 1) {
        x = a[0], d = 1;
        return;
    }
    set<int> mp;
    for (auto i : a) mp.insert(i);
    int t = a[1] - a[0];
    int tmp = 0;
    for (auto i : a) {
        if (mp.count((i + t) % m)) tmp ++;
    }
    int k = a.size() - tmp;
    x = a[0];
    d = 1ll * t * qp(k, m - 2, m) % m;
    while (mp.count((x - d + m) % m)) x = (x - d + m) % m;
    for (int i = 0; i < a.size(); ++i) {
        if (!mp.count((x + 1ll * i * d) % m)) {
            x = -1;
            return;
        }
    }
}

int main(int argc, char* argv[]) {
    scanf("%d%d", &m, &n);
    vector<int> a(n);
    for (int i = 0; i < n; ++i) scanf("%d", &a[i]);
    sort(a.begin(), a.end());
    if (n == 1) {
        printf("%d %d\n", a[0], 0);
        return 0;
    } else if (n == m) {
        for (int i = 0; i < n; ++i) {
            if (a[i] != i) {
                printf("-1\n");
                return 0;
            }
        }
        printf("0 1");
        return 0;
    }
    if (n * 2 < m) {
        solve(a);
    } else {
        vector<int> inva;
        int pos = 0;
        for (int i = 0; i < m; ++i) {
            if (pos < a.size() && a[pos] == i) pos ++;
            else inva.push_back(i);
        }
        solve(inva);
        if (x != -1) x = ((x - 1ll * n * d % m) % m + m) % m;
    }
    if (x == -1) printf("-1\n");
    else printf("%d %d\n", x, d);
    return 0;
}

D. Timofey and a flat tree

题意:对于一棵树,求一个根,使得本质不同子树个数最大。

题解:摆明了是树hash换根,如果在hash策略上做一些调整就可以很方便的做换跟。考虑对子树进行加法hash,也就是说\(hs[u]=1+\sum pow(p,hs[v])\),这样做的好处是对于减掉一个子树,只需要在模意义下做减法。考虑在\(u\to v\)这条边上做换根,显然对于\(hs[u]-=hs[v]\)\(hs[v]+=hs[u]\)就ok了,回溯的时候再逆向加回去。

代码:

/*================================================================
*
*   创 建 者: badcw
*   创建日期: 2020/5/10
*
================================================================*/
#include <bits/stdc++.h>

#define ll long long
using namespace std;

const int maxn = 1e5+50;
const int mod = 998244353;
ll qp(ll a, ll n, ll mod = ::mod) {
    ll res = 1;
    while (n > 0) {
        if (n & 1) res = res * a % mod;
        a = a * a % mod;
        n >>= 1;
    }
    return res;
}

int n;
int hs = 79;
vector<int> edge[maxn];
int dp[maxn];
map<int, int> mp;
int now;
int res, respos;

void add(int x) { if (mp[x] ++ == 0) now ++; }

void del(int x) { if (--mp[x] == 0) now --; }

void dfs(int u, int pre) {
    for (auto v : edge[u]) {
        if (v == pre) continue;
        dfs(v, u);
        dp[u] = (dp[u] + qp(hs, dp[v])) % mod;
    }
    dp[u] ++;
    add(dp[u]);
}

void dfs2(int u, int pre) {
    if (now > res) {
        res = now;
        respos = u;
    }
    for (auto v : edge[u]) {
        if (v == pre) continue;
        del(dp[u]);
        del(dp[v]);

        dp[u] = (dp[u] - qp(hs, dp[v])) % mod;
        if (dp[u] < 0) dp[u] += mod;

        dp[v] = (dp[v] + qp(hs, dp[u])) % mod;

        add(dp[u]);
        add(dp[v]);

        dfs2(v, u);

        del(dp[u]);
        del(dp[v]);

        dp[v] = (dp[v] - qp(hs, dp[u])) % mod;
        if (dp[v] < 0) dp[v] += mod;
        dp[u] = (dp[u] + qp(hs, dp[v])) % mod;

        add(dp[u]);
        add(dp[v]);
    }
}

int main(int argc, char* argv[]) {
    scanf("%d", &n);
    for (int i = 1; i < n; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        edge[u].push_back(v);
        edge[v].push_back(u);
    }
    dfs(1, 0);
    res = now;
    respos = 1;
    dfs2(1, 0);
    printf("%d\n", respos);
    return 0;
}

E. Timofey and our friends animals

题意:给一张图,每条边的节点编号差不超过5,m次询问\(l \to r\)这些节点构成多少个联通块。

题解:首先最直观的想法当然是线段树维护左右两端5个数字的联通性,光是想想代码量就上天。当然我也只会这样写,复杂度\(O(nlognk^2)\)。另外有一个做法是:对于\(l \to r\),联通块个数是其中包含的全局联通块个数+不超过10个块,这不超过10个块是\([l-5,l)\)\((r,r+5]\)决定的,具体来说还会有一些细节需要考虑,复杂度会降到\(O(nk^2)\)。还有LCT的做法,可以叉掉出题人,因为不需要编号不超过5这个条件,可以做任意图的联通块个数,总之这是个假题。

代码:

/*================================================================
*
*   创 建 者: badcw
*   创建日期: 2020/5/8
*
================================================================*/
#include <bits/stdc++.h>

#define ll long long
using namespace std;

const int maxn = 1e5 + 50;
const int mod = 1e9 + 7;


int n, m, K;
int edge[maxn][5];

struct node {
    int lpos, rpos;
    int val, len;
    int lval[5], rval[5];

    node() {
        lpos = rpos = 0;
        val = len = 0;
//        for (int i = 0; i < 5; ++i) pos[i].clear();
    }
} p[maxn << 2];

int le, re;
int pre[maxn];

int f(int x) { return x == pre[x] ? x : pre[x] = f(pre[x]); }

inline node comb(node a, node b) {
    if (a.len == 0) return b;
    node res;
    res.lpos = a.lpos, res.rpos = b.rpos;
    res.len = a.len + b.len;
    res.val = a.val + b.val;
    int llt = min(K, a.len);
    int rrt = min(K, b.len);
    int rt = min(K, res.len);
    for (int i = 0; i < llt; ++i) pre[a.rval[i]] = a.rval[i], pre[a.lval[i]] = a.lval[i];
    for (int i = 0; i < rrt; ++i) pre[b.lval[i]] = b.lval[i], pre[b.rval[i]] = b.rval[i];
    for (int i = a.rpos - llt + 1; i <= a.rpos; ++i) {
        for (int j = max(0, b.lpos - i - 1); j < K && j + i + 1 < b.lpos + rrt; ++j) {
            if (edge[i][j] == 0) continue;
            int now = i + j + 1;
            if (now > b.rpos || now < b.lpos) continue;
            int u = f(b.lval[now - b.lpos]), v = f(a.rval[a.rpos - i]);
            if (u != v) {
                res.val--;
                pre[u] = v;
            }
        }
    }
    for (int i = 0; i < llt; ++i) res.lval[i] = f(a.lval[i]);
    for (int i = 0; i < rt - llt; ++i) res.lval[i + llt] = f(b.lval[i]);
    for (int i = 0; i < rrt; ++i) res.rval[i] = f(b.rval[i]);
    for (int i = 0; i < rt - rrt; ++i) res.rval[i + rrt] = f(a.rval[i]);
    return res;
}

inline void build(int rt, int l, int r) {
    if (l == r) {
        p[rt].len = p[rt].val = 1;
        p[rt].lpos = p[rt].rpos = l;
        p[rt].lval[0] = l;
        p[rt].rval[0] = l;
        return;
    }
    int mid = l + r >> 1;
    build(rt << 1, l, mid);
    build(rt << 1 | 1, mid + 1, r);
    p[rt] = comb(p[rt << 1], p[rt << 1 | 1]);
}

inline node query(int rt, int l, int r) {
    if (le <= l && r <= re) return p[rt];
    int mid = l + r >> 1;
    node res;
    if (le <= mid) res = comb(res, query(rt << 1, l, mid));
    if (re > mid) res = comb(res, query(rt << 1 | 1, mid + 1, r));
    return res;
}

int main(int argc, char *argv[]) {
//    freopen("data.in","r",stdin);
//    freopen("data.out", "w", stdout);
//    clock_t ST = clock();
    scanf("%d%d", &n, &K);
    scanf("%d", &m);
    for (int i = 0; i < m; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        if (u > v) swap(u, v);
        edge[u][v - u - 1] = 1;
    }
    build(1, 1, n);
//    cerr << "time: " << ((clock() - ST) * 1000.0 / CLOCKS_PER_SEC) << "ms" << endl;
    int q;
    scanf("%d", &q);
    for (int i = 1; i <= q; ++i) {
        scanf("%d%d", &le, &re);
        printf("%d\n", query(1, 1, n).val);
    }
//    cerr << "time: " << ((clock() - ST) * 1000.0 / CLOCKS_PER_SEC) << "ms" << endl;
    return 0;
}
posted @ 2020-05-11 14:42  badcw  阅读(134)  评论(0编辑  收藏  举报