暑假集训五[星际旅行,砍树,超级树,求和]

暑假集训五

题面

别问为啥先更六再更五。

A.星际旅行

image

转化题意,因为无向边相当于两条有向边,让一条无向边只走一次,相当于删去它两条边中的一条。则题意可转化为删除两条边之后,原图是否存在欧拉路,以及能形成欧拉路的方案数。

存在欧拉路的充要条件是:图是边联通的
边联通和点联通的关系就像割点与割边。

删去两条边能构成欧拉路的,总共只有三种情况:

  1. 删除两个自环。
  2. 删除一个自环和一个正常边。
  3. 删除两条有且仅有一个公共顶点的边。

正确性的证明详见kiritokazuto大佬的博客

Code

#include<cstdio>
#include<algorithm>

using namespace std;

const int MAXN = 1e5 + 10;
int n, m, cnt, ring;
long long ans;
int head[MAXN], deg[MAXN];
bool used[MAXN];

struct Edge{
    int to, next;
}e[MAXN << 1];

inline void Add(int u, int v){
    e[++cnt].to = v;
    e[cnt].next = head[u];
    head[u] = cnt;
}

void dfs(int rt){
    used[rt] = true;

    for(register int i = head[rt]; i; i = e[i].next){
        int v = e[i].to;
        if(!used[v]) dfs(v);
    }
}

inline int read(){
    int x = 0, f = 1;
    char c = getchar();

    while(c < '0' || c > '9'){
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9'){
        x = (x << 1) + (x << 3) + (c ^ 48);
        c = getchar();
    }

    return x * f;
}

int main(){
    n = read(), m = read();
    for(register int i = 1; i <= m; i++){
        int u, v;
        u = read(), v = read();
        
        Add(u, v);
        Add(v, u);
        if(u == v) ring++;
        else{
            deg[u]++;
            deg[v]++;
        }
    }

    if(ring == m){
        putchar('0');
        return 0;
    }

    for(register int i = 1; i <= n; i++){
        if(deg[i]){
            dfs(i);
            break;
        }
    }
    
    for(register int i = 1; i <= n; i++){
        if(!used[i] && deg[i]){
            putchar('0');
            return 0;
        }
    }

    ans += 1LL * (m - ring) * ring;
    ans += 1LL * ring * (ring - 1) / 2;
    for(register int i = 1; i <= n; i++)
        ans += 1LL * deg[i] * (deg[i] - 1) / 2;
    
    printf("%lld", ans);

    return 0;
}

B.砍树

image

数论分块,等价于求出一个最大的 \(d\),满足:

\[\large \sum\limits_{i = 1}^{n}(\left \lceil \frac{a_i}{d} \right \rceil \times d - a_i)\leq k \]

\(C = k + \sum\limits_{i = 1}^{n}a_i\)

那么 \(\sum\limits_{i = 1}^{n} \lceil \frac{a_i}{d}\rceil \times d \leq C\)

然后就可以数论分块了。

Code

#include<cmath>
#include<cstdio>

#define LL long long

using namespace std;

const int MAXN = 110;
LL n, k;
LL sum, ans;
LL aim[MAXN];

inline LL read(){
    LL x = 0, f = 1;
    char c = getchar();

    while(c < '0' || c > '9'){
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9'){
        x = (x << 1) + (x << 3) + (c ^ 48);
        c = getchar();
    }

    return x * f;
}

int main(){
    //数论分块啊
    n = read(), k = read();
    for(register int i = 1; i <= n; i++){
        aim[i] = read();
        sum += aim[i];
    }
        
    sum += k;
    for(register LL l = 1, r; l <= sum; l = r + 1){
        r = sum / (sum / l);
        LL tot = 0;
        for(register int i = 1; i <= n; i++)
            tot += ceil((1.0 * aim[i]) / (1.0 * r));
        
        if(tot <= sum / r) ans = r;
    }

    printf("%lld", ans);

    return 0;
}

C.超级树

神仙DP,不会,贺的。

image

还是看kiritokazuto大佬的博客吧。

Code

#include<cstdio>
#include<algorithm>

#define LL long long

using namespace std;

const int MAXK = 310;
int k, p;
LL dp[MAXK][MAXK]; //这状态定义能想到就TM见鬼了
// dp[i][j]表示在一棵 i 级超级树中,有 j 条路径同时存在且这 j 条路径没有公共点时,可能的情况数

int main(){
    scanf("%d%d", &k, &p);

    dp[1][1] = dp[1][0] = 1;
    for(register int i = 0; i <= k; i++){
        for(register int j = 0; j <= k; j++){
            for(register int l = 0; l <= j; l++){
                int r = j - l;
                LL num = dp[i][l] * dp[i][r] % p;
                dp[i + 1][j + 1] = (1LL * dp[i + 1][j + 1] + num) % p;
                dp[i + 1][j] = (1LL * dp[i + 1][j] + num + 2 * num * j) % p;
                dp[i + 1][j - 1] = (1LL * dp[i + 1][j - 1] + 2 * num * l * r + num * (l * (l - 1) + r * (r - 1))) % p;
            }
        }
    }

    printf("%lld", dp[k][1]);

    return 0;
}

D.求和

P4427 [BJOI2018]求和

大水题,LCA树剖维护 + 树上差分。

可以观察到,\(k <= 50\)\(n <= 3e5\),并不大。
一棵树的平均深度是 \(\log n\),退化成链的最坏深度是 \(n\),所以我们可以预处理出 \(1\)~\(n\)\(1\)~\(k\) 次幂,同时维护一个 \(1\) ~ \(k\) 次幂的树上前缀和。

最后的答案就是

\[sum[u][k] + sum[v][k] - 2 * sum[lca][k] + val[lca][k] \]

记得取模后被减数可能小于减数,所以减完要先加上模数再取模,考试的时候因为这个 100 pts →
0 pts。

Code

//没大样例,手搓的倒是没啥问题 
//linux怎么架对拍来着,囸 
//卡个常,毕竟大常数选手 
#include<cstdio>
#include<algorithm>

#define LL long long

using namespace std;

const int MAXN = 3e5 + 10, MAXK = 55;
const int Mod = 998244353;
int n, m, cnt, num;
int head[MAXN];
int fa[MAXN], son[MAXN], size[MAXN], deep[MAXN];
int dfn[MAXN], top[MAXN];
LL p[MAXN][MAXK], sum[MAXN][MAXK];

struct Edge{
    int to, next;
}e[MAXN << 1];

inline void Add(int u, int v){
    e[++cnt].to = v;
    e[cnt].next = head[u];
    head[u] = cnt;
}

void dfs_deep(int rt, int father, int depth){
    size[rt] = 1;
    fa[rt] = father;
    deep[rt] = depth;
    
    for(register int i = 1; i <= 50; i++)
        sum[rt][i] = 1LL * (sum[fa[rt]][i] + p[deep[rt]][i]) % Mod;

    int max_son = -1;
    for(register int i = head[rt]; i; i = e[i].next){
        int v = e[i].to;
        if(v == father) continue;

        dfs_deep(v, rt, depth + 1);
        size[rt] += size[v];

        if(max_son < size[v]){
            son[rt] = v;
            max_son = size[v];
        }
    }
}

void dfs_top(int rt, int top_fa){
    dfn[rt] = ++num;
    top[rt] = top_fa;

    if(!son[rt]) return;
    dfs_top(son[rt], top_fa);

    for(register int i = head[rt]; i; i = e[i].next){
        int v = e[i].to;
        if(!dfn[v]) dfs_top(v, v);
    }
}

int Get_Lca(int x, int y){
    while(top[x] != top[y]){
        if(deep[top[x]] < deep[top[y]]) swap(x, y);
        x = fa[top[x]];
    }

    if(deep[x] > deep[y]) swap(x, y);

    return x;
}

LL Get_Sum(int x, int y, int k){
    int lca = Get_Lca(x, y);
    return (1LL * (sum[x][k] + sum[y][k]) % Mod - (2 * sum[lca][k] % Mod) + Mod + p[deep[lca]][k]) % Mod;
}

void init(){
    for(register int i = 1; i <= n; i++){
        p[i][0] = 1;
        for(register int j = 1; j <= 50; j++)
            p[i][j] = 1LL * p[i][j - 1] * i % Mod;
    }
}

inline int read(){
    int x = 0, f = 1;
    char c = getchar();

    while(c < '0' || c > '9'){
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9'){
        x = (x << 1) + (x << 3) + (c ^ 48);
        c = getchar();
    }

    return x * f;
} 

int main(){
    n = read();
    init();

    for(register int i = 1; i <= n - 1; i++){
        int u, v;
        u = read(), v = read();
        Add(u, v);
        Add(v, u);
    }

    dfs_deep(1, 0, 0);
    dfs_top(1, 1);

    m = read();
    for(register int i = 1; i <= m; i++){
        int x, y, k;
        x = read(), y = read(), k = read();
        printf("%lld\n", 1LL * Get_Sum(x, y, k));
    }

    return 0;
}
posted @ 2022-08-18 22:17  TSTYFST  阅读(18)  评论(0编辑  收藏  举报