7月22集训个人赛第三场

A题,简单图论题: Codeforces 295B floyd变种

题意:对于一个带权有向图,每次删除一个点,要求计算输出删除该点前当前图的所有点间的最短路径之和。

解法:对于删除顺序,我们可以反过来做,这样就相当于每次添加一个点到图中,然后询问当前图的所有点间的最短路径之和。对于每次添加操作,就相当于用当前添加的点v,去更新整个图的最短路,也就是dist[s][t] = min(dist[s][t], dist[s][v]+dist[v][t]);

对于统计操作,直接暴力枚举即可。整个复杂度为O(n^3),唯一需要注意的就是,本题数据范围,需要用long long 来处理。

#include <stdio.h>
#include <string.h>
#define maxn 505
typedef long long LL;

bool mark[maxn];
LL dist[maxn][maxn];
LL seq[maxn];
LL ans[maxn];

LL min(LL x, LL y){
    return x < y ? x : y;
}

void update(int s, int n){
    for(int v = 1; v <= n; v ++){
        for(int u = 1; u <= n; u ++){
            dist[v][u] = min(dist[v][u], dist[v][s] + dist[s][u]);
        }
    }
}
LL query(int s, int n){
    LL ret = 0;
    for(int i = n, v = seq[i]; i >= s; v = seq[--i]){
        for(int j = n, u = seq[j]; j >= s; u = seq[--j]){
            ret += dist[v][u];
        }
    }
    return ret;
}
int main(){
    for(int n; scanf("%d", &n)!=EOF; ){
        for(int i = 1; i <= n; i ++){
            for(int j = 1; j <= n; j ++)
                scanf("%I64d", &dist[i][j]);
            mark[i] = false;
        }
        for(int i = 1;i <= n; i ++)
            scanf("%d", &seq[i]);
        for(int i = n; i >= 1; i --){
            mark[seq[i]] = 1;
            update(seq[i], n);
            ans[i] = query(i, n);
        }
        for(int i = 1; i <= n; i ++){
            printf("%I64d ", ans[i]);
        }
        printf("\n");
    }
    return 0;
}

B题,简单数学题: Codeforces 154B

题意:有n个人,m个操作。每个人有一个编号i,每个人有两种行为,行为+ i,表示编号为i的人需要去做实验,此时需要满足当前正在工作的人(设编号为j)有gcd(i, j) = 1;行为- i表示编号为i的人退出实验。对于每个操作需要判断是否成功。

解法:操作中最麻烦的就是怎么样查找谁与谁冲突。考虑i,j如果冲突,也就是说gcd(i, j)!=1,那么我们可以对gcd(i, j)分解为若干个素数的乘积,设为pi,显然存在且只存在一个j,使得pi可以整除gcd(i, j)。(否则设存在k与i冲突且pi整除gcd(k, i),那么k肯定与j冲突。) 

处理:首先预处理出10^5内的素数,然后维护一个数组用于表示对应素数的倍数,初始化为0。然后对于+操作,进行素数分解,然后对于每个素数进行查询倍数,如果不是0,就出现了冲突。对于- 操作,同样进行素数分解,并将对应的素数的倍数赋值为0.对于已操作过,这个可以直接利用一个数组进行标记。整个复杂度为O(nlogn  + nsqrt(n)).

#include <stdio.h>
#include <string.h>

#define maxn 100000
bool f[maxn+5];
int pos[maxn];
int p[maxn/10], g[maxn/10];
int total;
void process(int n){
    memset(f, 0, sizeof(f));
    total = 0;
    for(int i = 2; i <= n; i ++){
        if(!f[i]){
            pos[i] = total;
            p[total ++] = i;
            for(int j = i; j <= n; j += i){
                f[j] = 1;
            }
        }
    }
    memset(f, 0, sizeof(f));
    memset(g, 0, sizeof(g));
}
int solve(int n){
    for(int i = 2; i*i <= n; i ++){
        if(n%i==0){
            if(g[pos[i]]) return g[pos[i]];
            while(n%i==0) n/=i;
        }
    }
    if(n!=1 && g[pos[n]]) return g[pos[n]];
    return 0;
}
int refresh(int n, int x){
    for(int i = 2; i*i<=n; i ++){
        if(n%i==0){
            g[pos[i]] = x;
            while(n%i==0) n/=i;
        }
    }
    if(n!=1) g[pos[n]] = x;
    return 0;
}
int main(){
    int n, m, x;
    char cmd[3];
    scanf("%d%d", &n, &m);
    process(n);
    for(int i = 0; i < m; i ++){
        scanf("%s%d", cmd, &x);
        if(cmd[0]=='+'){
            if(f[x]){
                printf("Already on\n");
                continue;
            }
            int t = solve(x);
            if(t) printf("Conflict with %d\n", t);
            else {
                f[x] = 1;
                refresh(x, x);
                printf("Success\n");
            }
        }
        else{
            if(!f[x]) printf("Already off\n");
            else f[x] = 0, refresh(x, 0), printf("Success\n");
        }
    }
    return 0;
}

C题,简单数学题 Codefoces 150B

题意:对于一个大小为M的字符集,求长度为n的字符串的个数,要求字符串满足对于所有长度为k的连续子串为回文串。

解法:由于要求长度为k的字符串必须为回文串。那么我们可以先预处理一下哪些位置必须放同样的字符(建图操作)。这样我们就可以将整个位置分成x个集合(直接染色),同一个集合内的位置能放的字符必定是一样的。对于每个集合能放的字符的种类是一样的,所以就是m^x.整个算法的复杂度为O((n-k)*k/2 + n + log m)

 

 1 #include <stdio.h>
 2 #include <string.h>
 3 #define maxn 2005
 4 typedef long long LL;
 5 const LL mod = 1000000007;
 6 
 7 int g[maxn][maxn];
 8 int col[maxn], cnt;
 9 
10 LL quick_power(LL a, LL b){
11     LL r = 1;
12     for(;b;b>>=1){
13         if(b&1) r = a * r %mod;
14         a = a * a %mod;
15     }
16     return r;
17 }
18 void dfs(int v, int f, int c, int n){
19     col[v] = c;
20     for(int i = 1; i <= n; i ++){
21         if(!g[v][i]|| i == f || col[i]) continue;
22         dfs(i, v, c, n);
23     }
24 }
25 int main(){
26     int n, m, k;
27     scanf("%d%d%d", &n, &m, &k);
28     if(k > n){
29         printf("%I64d\n", quick_power(m, n));
30         return 0;
31     }
32     for(int i = 1; i + k - 1<= n; i ++){
33         for(int a = i, b = i + k - 1; a < b; a ++ , b --){
34             g[a][b] = g[b][a] = 1;
35         }
36     }
37     for(int i = 1; i <= n; i ++){
38         if(!col[i]){
39             cnt ++;
40             dfs(i, -1, cnt, n);
41         }
42     }
43     printf("%I64d\n", quick_power(m, cnt));
44     return 0;
45 }

 

 

D题,数据结构:线段树 Codefoces 266E

题意:这里就不在描述了,题目很短= =

解法:这个题和杭州邀请赛的C题是同一类型的。主要是对于 

的计算。0<=k<=5,可以考虑直接展开(i-l+1)^k:

k = 0: 1

k = 1: i - l  + 1

k = 2: i^2 - 2(l-1)i + (l-1)^2

k = 3: i^3 - 3i^2(l-1) + 3i(l-1)^2 - (l-1)^3

k = 4: i^4 - 4i^3(l-1) + 6i^2(l-1)^2 - 4i(l-1)^3 + (l-1)^4

k = 5: i^5 - 5i^4(l-1) + 10i^3(l-1)^2 - 10i^2(l-1)^3 + 5i(l-1)^4 - (l-1)^5

 

那么由于l-1项只与l有关,我们只需要用线段树维护:i^5ai , i^4ai, i^3ai, i^2ai, iai, ai的区间和。然后剩余的就是线段树的工作了。对于查询操作,可以直接将区间的上述和直接计算到,然后可以用按照二项式展开统一计算。

#include <stdio.h>
#include <string.h>

#define maxn 100005
#define lson(x) (x<<1)
#define rson(x) (x<<1|1)
#define md(x, y) ((x+y)>>1)
typedef long long LL;
#define MOD 1000000007
LL val[6][maxn];
LL ans[6];
int C[6][6];

struct Tree{
    LL f[6][maxn*4];
    LL v[maxn*4];
    void clear(){
        memset(f, 0, sizeof(f));
        memset(v, 0, sizeof(v));
    }
    void build(int c, int left, int right){
        if(left == right){
            scanf("%I64d", &v[c]);
            LL t = 1;
            for(int i = 0; i <= 5; i ++){
                f[i][c] = t * v[c]%MOD;
                t = t * left %MOD;
            }
            return ;
        }
        int m = md(left, right);
        build(lson(c), left, m);
        build(rson(c), m + 1, right);
        push_up(c);
    }
    void push_down(int c, int left, int right){
        if(v[c] == -1) return;
        int l = lson(c), r = rson(c);
        v[l] = v[r] = v[c];
        int m = md(left, right);
        for(int i = 0; i <= 5; i ++){
            f[i][l] = v[l] * (((val[i][m] - val[i][left-1])%MOD + MOD)%MOD)%MOD;
            f[i][r] = v[r] * (((val[i][right] - val[i][m])%MOD + MOD)%MOD)%MOD;
            f[i][c] = 0;
        }
        v[c] = -1;
    }
    void push_up(int c){
        int l = lson(c), r = rson(c);
        for(int i = 0; i <= 5; i ++){
            f[i][c] = (f[i][l] + f[i][r])%MOD;
        }
        v[c] = -1;
    }
    void update(int c, int l, int r, int left, int right, LL x){
        if(l <= left && right <= r){
            v[c] = x;
            for(int i = 0; i <= 5; i ++){
                f[i][c] = x*(((val[i][right] - val[i][left-1])%MOD + MOD)%MOD)%MOD;
            }
            return ;
        }
        push_down(c, left, right);
        int m = md(left, right);
        if(r <= m) update(lson(c), l, r, left, m, x);
        else if(l > m) update(rson(c), l, r, m + 1, right, x);
        else{
            update(lson(c), l, m, left, m, x);
            update(rson(c), m + 1, r, m + 1, right, x);
        }
        push_up(c);
    }
    void query(int c, int l, int r, int left, int right, LL ans[]){
        if(l <= left && right <= r){
            for(int i = 0; i <= 5; i ++){
                ans[i] = (f[i][c] + ans[i])%MOD;
            }
            return ;
        }
        push_down(c, left,right);
        int m = md(left, right);
        if(r <= m) query(lson(c), l, r, left, m, ans);
        else if(l > m) query(rson(c), l, r, m+1, right, ans);
        else {
            query(lson(c), l, m, left, m, ans);
            query(rson(c), m + 1, r, m + 1, right, ans);
        }
        push_up(c);
    }
};
Tree tree;
void init(int n){
    for(int i = 1; i <= n; i ++){
        LL t = 1;
        for(int j = 0; j <= 5; j ++){
            val[j][i] = (val[j][i-1] + t)%MOD;
            t = (t * i)%MOD;
        }
    }
    C[0][0] = 1;
    C[1][1] = C[1][0] = 1;
    for(int i = 2; i <= 5; i ++){
        C[i][0] = 1;
        for(int j = 1; j <= i; j ++){
            C[i][j] = C[i-1][j] + C[i-1][j-1];
        }
    }
}

int main(){
    int n, m, l, r, x;
    char cmd[2];
    LL t;
    scanf("%d%d", &n, &m); getchar();
    init(n);
    tree.build(1, 1, n);
    for(;m --;){
        scanf("%s%d%d%d", cmd, &l, &r, &x);
        if(cmd[0] == '?'){
            memset(ans, 0, sizeof(ans));
            tree.query(1, l, r, 1, n, ans);
            LL result = 0; t = 1;
            for(int i = x, j = 0; i >=0; i --){
                if(j&1) result = (result - ((ans[i]*t)%MOD)*C[x][j])%MOD;
                else result = (result + ((ans[i]*t)%MOD)*C[x][j])%MOD;
                t = (t * (l-1))%MOD; j ++;
            }
            printf("%I64d\n", (result+MOD)%MOD);
        }
        else tree.update(1, l, r, 1, n, x);
    }
    return 0;
}

E题:DP题,一个简单的树形DP, Codefoces 161D

题意:题意比较简单,不在做描述了.

解法:简单树形DP。

答案分成两部分:

1.a, b两个点的距离为k,且,a为b的祖先

2.a, b两个点的距离为k,a,b分别在两颗不同的子树上。

dp[i][k]表示,以i为根的子树上,与i的距离为k的节点个数。

那么显然:dp[i][k] = sum{dp[s][k-1]}其中s是i的孩子节点。

对于第一部分的答案,显然就是dp[i][k]

对于第二部分的答案,是sum{dp[i][a]*dp[s][b]}其中s是当前待处理的孩子节点。

#include <stdio.h>
#include <string.h>
#include <vector>
using namespace std;
#define maxn 50005

vector<int> vec[maxn];
int dp[maxn][505];
long long ans;

void dfs(int v, int f, int k){
    for(int i = 0; i <= k; i ++)
        dp[v][i] = 0;
    dp[v][0] = 1;
    for(int i = 0; i < vec[v].size(); i ++){
        int u = vec[v][i];
        if(u==f) continue;
        dfs(u, v, k);
        for(int j = 0; j < k; j ++){
            ans += dp[u][j] * dp[v][k - j - 1];
        }
        for(int j = 1; j < k; j ++){
            dp[v][j] += dp[u][j-1];
        }
    }
    ans += dp[v][k];
}

int main(){
    for(int n, k; scanf("%d%d", &n, &k)!=EOF; ){
        for(int i = 1; i <= n; i ++)
            vec[i].clear();
        for(int i = 1, x, y; i < n; i ++){
            scanf("%d%d", &x, &y);
            vec[x].push_back(y);
            vec[y].push_back(x);
        }
        ans = 0;
        dfs(1, 0, k);
        printf("%I64d\n", ans);
    }
    return 0;
}

F题,比较有意思的字符串题,这个是智商题,表示智商拙计。Codeforces 224D

题意:给定两个串S和T,求是不是对于所有的Si,至少存在一个子串sub, sub是S的子串(不一定连续),Si属于sub,且sub==T.

解法:对于S串的每个位置i,求出以i为终点的S1...Si与T1...Tt的最长公共子序列的长度l[i],

然后求出以i为终点的Ss..Si+1Si与Tt...T1的最长公共子序列的长度r[i]。

然后直接判断向左匹配的最大长度l[i]与向右匹配的最大长度r[i],是不是有l[i]+r[i]-1 >= t。

由于只要求解长度,我们对于每个字符可以记录一个最长匹配长度,这样就可以做到O(n)的预处理匹配长度,然后可以做到O(n)的判断。

 

 1 #include <stdio.h>
 2 #include <string.h>
 3 #define maxn 200005
 4 
 5 char s[maxn], t[maxn];
 6 int l[maxn], r[maxn], pos[26];
 7 
 8 int main(){
 9     scanf("%s%s", s + 1, t + 1);
10     int ls = strlen(s + 1), lt = strlen(t + 1);
11 
12     memset(pos, -1, sizeof(pos));
13     for(int i = 1, j = 1; s[i]; i ++){
14         if(t[j] && s[i]==t[j]){
15             pos[s[i]-'a'] = j++;
16         }
17         l[i] = pos[s[i] - 'a'];
18     }
19     memset(pos, -1, sizeof(pos));
20     for(int i = ls, j = 1; i > 0; i --){
21         if(j <= lt && s[i]==t[lt - j + 1]){
22             pos[s[i]-'a'] = j ++;
23         }
24         r[i] = pos[s[i]-'a'];
25     }
26     bool f = true;
27     for(int i = 1; s[i]; i ++){
28         if(l[i] == -1 || r[i] == -1 || l[i] + r[i] < lt + 1){
29             f = false;
30             break;
31         }
32     }
33     puts(f? "Yes" : "No");
34     return 0;
35 }

 

 

原文地址:https://www.cnblogs.com/bootstar/p/3204702.html