Codeforces Round #369 (Div2) ABCDE

A: 暴力
B: 暴力
C: dp,可以暴力,也可以不暴力
D: 拓扑排序,dfs,方案计数
E: 数学,有意思

比赛链接:Codeforces Round #369 (Div2)

A. Bus to Udayland

题意:
给一个 n 排的公交车座位,每排有四个位置,左右各两个,中间是过道,空的座位用O表示,非空的座位用X表示。要从中找到相邻的两个空的座位并输出。

数据范围:n1000

分析:

暴力扫即可。

时间复杂度: O(n)

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAX_N = 1010;

int n;
char s[MAX_N][10];

int main()
{
    while (~scanf("%d", &n)) {
        int flag = 0;
        for(int i = 0; i < n; ++i) {
            scanf("%s", s[i]);
            if (flag) continue;
            if (s[i][0] == 'O' && s[i][1] == 'O') {
                s[i][0] = s[i][1] = '+';
                flag = 1;
            } else if (s[i][3] == s[i][4] && s[i][3] == 'O') {
                s[i][3] = s[i][4] = '+';
                flag = 1;
            }
        }
        if (flag == 0) printf("NO\n");
        else {
            printf("YES\n");
            for (int i = 0; i < n; ++i) {
                printf("%s\n", s[i]);
            }
        }
    }
    return 0;
}

B. Chris and Magic Square

题意:

给一个 nn 的矩阵,只有一个位置的元素是未知的用0表示,其余位置都是正整数,要求将未知的位置填上一个正整数 X ,使得每一行,每一列,主对角线和副对角线的元素之和都相等。输出X,如果无解输出-1。

数据范围: n500,data[i][j]109

分析:

暴力扫求和,然后判断即可。注意开long long

时间复杂度: O(n2)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <climits>
#include <cmath>
#include <ctime>
#include <cassert>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
using namespace std;
typedef long long ll;
const int MAX_N = 510;

int n;
ll mat[MAX_N][MAX_N], r[MAX_N], c[MAX_N], a, b, x, y;

void solve()
{
    if (n == 1) {
        printf("1\n");
        return ;
    }
    ll ans;
    if (x == n) ans = r[n - 1] - r[n];
    else ans = r[x + 1] - r[x];
    c[y] += ans;
    r[x] += ans;
    if (x == y) a += ans;
    if (x == n - y + 1) b += ans;
    int flag = 1;
    if (a != b || ans < 0) flag = 0;
    for (int i = 1; i <= n; ++i) {
        if (a != r[i] || a != c[i]) {
            flag = 0;
            break;
        }
    }
    if (flag && ans > 0) printf("%I64d\n", ans);
    else printf("-1\n");
}

int main()
{
    while (~scanf("%d", &n)) {
        memset(mat, 0, sizeof(mat));
        memset(r, 0, sizeof(r));
        memset(c, 0, sizeof(c));
        a = b = 0;
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= n; ++j) {
                scanf("%I64d", &mat[i][j]);
                if (i == j) a += mat[i][j];
                if (i == n - j + 1) b += mat[i][j];
                r[i] += mat[i][j];
                c[j] += mat[i][j];
                if (mat[i][j] == 0) {
                    x = i, y = j;
                }
            }
        }
        solve();
    }
    return 0;
}

C. Coloring Trees

题意:

给一排 n 棵树,每棵树初始时会被染色,用正整数表示,如果未被染色就是0.这一排树连续的一段相同颜色的树被称为一段。没被染色的树可以用m种颜色来染色,会给出一个 nm 的代价矩阵。现在要将这一排树恰好分成 K 段,并且每棵树都要染色,求最小代价。无解输出-1.

数据范围:n,m,K100,w[i][j]109

分析:

比赛时一直在想 O(nmK) dp,差一点就写完了(捂脸.jpg)。赛后看到大佬们都是 O(nm2K) dp,我的内心其实是崩溃的。

O(nmK) dp我是用 dp[i][j] 表示将前 i 棵树分成j段的最小代价,还要记录一下最小代价时第 i 个树的颜色id[i][j]和次小代价 dpp[i][j] 。然后状态转移。需要记录每棵树颜色为 t 时分成j段的最小代价: vis[i][t][j]

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <climits>
#include <cmath>
#include <ctime>
#include <cassert>
#include <set>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
using namespace std;
typedef long long ll;
const int MAX_N = 110;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3fll;

int n, m, K;
int color[MAX_N], id[MAX_N][MAX_N];
ll dp[MAX_N][MAX_N], dpp[MAX_N][MAX_N], w[MAX_N][MAX_N];
ll vis[MAX_N][MAX_N][MAX_N];

void update(int i, int j, int t, ll tmp)
{
    if (tmp + w[i][t] < dp[i][j]) {
        dpp[i][j] = dp[i][j];
        dp[i][j] = tmp + w[i][t];
        id[i][j] = t;
    }  else if (tmp + w[i][t] < dpp[i][j]) {
        dpp[i][j] = tmp + w[i][t];
    }
}

void solve()
{
    memset(dp, 0x3f, sizeof(dp));
    memset(vis, 0x3f, sizeof(vis));
    memset(id, 0, sizeof(id));
    if (color[1]) {
        dp[1][1] = 0, id[1][1] = color[1];
        vis[1][color[1]][1] = 0;
    } else {
        for (int t = 1; t <= m; ++t) {
            vis[1][t][1] = w[1][t];
            update(1, 1, t, 0);
        }
    }
    ll tmp;
    for (int i = 2; i <= n; ++i) {
        if (color[i]) { // 当前颜色确定
            for (int j = 1; j <= min(K, i); ++j) {
                if (color[i - 1]) {
                    if (color[i] == color[i - 1]) dp[i][j] = dp[i - 1][j];
                    else dp[i][j] = dp[i - 1][j - 1];
                    vis[i][color[i]][j] = dp[i][j];
                } else {
                    if (id[i - 1][j - 1] == color[i]) tmp = dpp[i - 1][j - 1];
                    else tmp = dp[i - 1][j - 1];
                    if (id[i - 1][j] == color[i]) tmp = min(tmp, dp[i - 1][j]);
                    tmp = min(tmp, vis[i - 1][color[i]][j]); // 这个判断很重要!
                    vis[i][color[i]][j] = tmp;
                    update(i, j, 0, tmp);
                }
                id[i][j] = color[i];
            }
        } else { // 当前颜色不确定
            for (int j = 1; j <= min(K, i); ++j) {
                if (color[i - 1]) { // 前一颗树颜色确定 
                    for (int t = 1; t <= m; ++t) {
                        if (t == color[i - 1]) {
                            tmp = dp[i - 1][j];
                        } else {
                            if (id[i - 1][j - 1] == t) tmp = dpp[i - 1][j - 1];
                            else tmp = dp[i - 1][j - 1];
                            if (id[i - 1][j] == t) tmp = min(tmp, dp[i - 1][j]);
                        }
                        tmp = min(tmp, vis[i - 1][t][j]); // 这个判断很重要!
                        vis[i][t][j] = tmp + w[i][t];
                        update(i, j, t, tmp);
                    }
                } else { // 前一棵树颜色不确定
                    for (int t = 1; t <= m; ++t) { // 枚举当前树的颜色
                        if (id[i - 1][j - 1] == t) tmp = dpp[i - 1][j - 1];
                        else tmp = dp[i - 1][j - 1];
                        if (id[i - 1][j] == t) tmp = min(tmp, dp[i - 1][j]);
                        tmp = min(tmp, vis[i - 1][t][j]); // 这个判断很重要!
                        vis[i][t][j] = tmp + w[i][t];
                        update(i, j, t, tmp);
                    }
                }
            }
        }
    }
    if (dp[n][K] == INF) dp[n][K] = -1;
    printf("%I64d\n", dp[n][K]);
}

int main()
{
    while (~scanf("%d%d%d", &n, &m, &K)) {
        int pre = 0, cnt = 0;
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &color[i]);
            if (color[i] && color[i] != pre) {
                cnt ++;
            }
            pre = color[i];
        }
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= m; ++j) {
                scanf("%I64d", &w[i][j]);
            }
        }
        if (K > n || cnt > K) printf("-1\n");
        else solve();
    }
    return 0;
}

O(nm2K) dp就没什么好讲的了。不过这种dp跑了300+ms,而上面的只有30+ms。

#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>
using namespace std;
typedef long long ll;
const int MAX_N = 110;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3fll;

int n, m, K;
int color[MAX_N];
ll w[MAX_N][MAX_N], dp[MAX_N][MAX_N][MAX_N];

void solve()
{
    memset(dp, 0x3f, sizeof(dp));
    if (color[1]) dp[1][color[1]][1] = 0;
    else {
        for (int j = 1; j <= m; ++j) {
            dp[1][j][1] = w[1][j];
        }
    }
    for (int i = 2; i <= n; ++i) {
        for (int j = 1; j <= min(j, K); ++j) {
            if (color[i]) {
                for (int p = 1; p <= m; ++p) {
                    if (p == color[i]) dp[i][color[i]][j] = min(dp[i][color[i]][j], dp[i - 1][p][j]);
                    else dp[i][color[i]][j] = min(dp[i][color[i]][j], dp[i - 1][p][j - 1]);
                }
            } else {
                for (int t = 1; t <= m; ++t) {
                    for (int p = 1; p <= m; ++p) {
                        if (p == t) dp[i][t][j] = min(dp[i][t][j], dp[i - 1][p][j] + w[i][t]);
                        else dp[i][t][j] = min(dp[i][t][j], dp[i - 1][p][j - 1] + w[i][t]);
                    }
                }
            }
        }
    }
    ll ret = INF;
    for (int i = 1; i <= m; ++i) {
        ret = min(ret, dp[n][i][K]);
    }
    if (ret == INF) ret = -1;
    printf("%I64d\n", ret);
}

int main()
{   
    while (~scanf("%d%d%d", &n, &m, &K)) {
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &color[i]);
        }
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= m; ++j) {
                scanf("%I64d", &w[i][j]);
            }
        }
        solve();
    }
    return 0;
}

D.Directed Roads

题意:

给一个 n 个点的有向图,每个点有且仅有一条边从它指出去。可以翻转一条边的方向:AB变成 BA ,使得这张图无有向环。翻转后允许一个点有多条边从它指出去,也可以没有边从它指出去。求方案数。结果对1e9+7取模。

数据范围: n2105

分析:

这题让我想起了之前的一道题:Codeforces 699 D Fix a Tree有兴趣的可以看看。

要注意到几个事实。首先所有的有向环不会相交的,因为每个点只会伸出去一条边。其次,我们假设单链和指向环的边的个数为 t ,那么此时的方案数是2t,因为每条边都可以选择翻转或者不翻转而且最终也不会形成环。再其次就是如果一个环里有 k 条边,那么方案数是;2k2,每条边有两种选择但是不能所有边都不翻转,也不能所有边都翻转,所以要减2.

那么剩下的就简单了。我是用拓扑排序处理出 t ,然后用dfs处理出每个环的边的个数。

时间复杂度: O(n)

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long ll;
const int MAX_N = 200010;
const ll mod = 1e9 + 7;

int n;
int to[MAX_N], degree[MAX_N], vis[MAX_N];
ll pw2[MAX_N];
queue<int> que;

void init()
{
    pw2[0] = 1;
    for (int i = 1; i < MAX_N; ++i) {
        pw2[i] = pw2[i - 1] * 2 % mod;
    }
}

int TopoSort()
{
    int ret = 0;
    while (!que.empty()) que.pop();
    for (int i = 1; i <= n; ++i) {
        if (degree[i] == 0) que.push(i);
    }
    while (!que.empty()) {
        int cur = que.front();
        que.pop();
        ret++;
        if (--degree[to[cur]] == 0) que.push(to[cur]);
        to[cur] = 0;
    }
//  printf("TopoSort = %d\n", ret);
    return ret;
}

void dfs(int u, int& depth)
{
    if (to[u] == 0) return;
    depth++;
    int v= to[u];
    to[u] = 0;
    dfs(v, depth);
}

void wyr()
{
    ll ret = pw2[TopoSort()];
    memset(vis, 0, sizeof(vis));
    for (int i = 1; i <= n; ++i) {
        if (to[i]) {
            int d = 0;
            dfs(i, d);
        //  printf("i = %d d = %d\n", i, d);
            ret = ret * (pw2[d] - 2) % mod;
        }
    }
    printf("%I64d\n", ret);
}

int main()
{
    init();
    while (~scanf("%d", &n)) {
        memset(degree, 0, sizeof (degree));
        for (int i = 1; i <= n; ++i) {
            scanf("%d", &to[i]);
            degree[to[i]]++;
        }
        wyr();
    }
    return 0;
}

E. ZS and The Birthday Paradox

题意:

2n 天(日期)和 k 个人,求至少有两个人的生日在同一天的概率?用分数表示。

数据范围:n1018,k1018

分析:

把问题转化一下,先求一下反面:两两不同的概率。这个很简单: Ck2n(2n)k .

剩下的就比较有意思了。

  • 你要想到把这个式子简化一下: (2n1)(2n2)(2n(k1))2n(k1)
    注意到分母只是2的幂,那么分子和分母的最大公约数也只能是2的幂。
  • 你要想到当 2nt(t<2n) 时找到最大的 p 使得2p|(2nt)等价于找到最大的 p 使得2p|t

证明:假设: 2nt=r2p ,其中 r 为奇数,此时p最大。那么移项可得: t=2nr2p=(2npr)2p ,显然 2npr 也是奇数,得证。

  • 你要想到求分子的最大的2的幂次的因子相当于求 (k1)! 的最大的2的幂次的因子。
  • 你要想到上面一步是等于: i=1k12i
  • 你要想到求出来分子分母的最大公约数后还要用到逆元(因为有模数)。
  • 你要想到当 k2n 的时候答案恒为1/1。
  • 你要想到因为分子是连续的一串数相乘,那么分别对mod取余后结果肯定也是连续的。当 k1mod 时,取余的结果肯定会出现0,所以分子肯定是0.当 k1mod 时,可以递推暴力算。因为mod只有1e6+3,而且是个素数。
  • 你要想到分母的幂是 n(k1) 是会爆long long的,你可以这样:quick_pow(quick_pow(2, n), k - 1)
    或者根据费马小定理:
    2mod1mod(mod)

    对幂次进行降幂: (n%(mod1))((k1)%(mod1))%(mod1) ,这样子就不会爆了。

时间复杂度: O(mod+log(k)+log(n))

好累。。。。。。

#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>
using namespace std;
typedef long long ll;
const ll mod = 1000003;

int num[mod + 10], sum[mod + 10];

ll quick_pow(ll a, ll b)
{
    ll ret = 1, tmp = a;
    while (b) {
        if (b & 1) ret = ret * tmp % mod;
        tmp = tmp * tmp % mod;
        b >>= 1;
    }
    return ret;
}

ll work(ll x)
{
    --x;
    ll ret = 0, base = 2;
    while (x / base) {
        ret += (x / base);
        base <<= 1;
    }
    return ret;
}

int main()
{
    ll n, K;
    while (~scanf("%I64d%I64d", &n, &K)) {
        if (n <= 63 && K > (1LL << n)) {
            printf("1 1\n");
            continue;
        }
        ll base = quick_pow(2, n);
        ll B = quick_pow(quick_pow(2, mod - 2), work(K)); // 逆元
        ll A = 1; // numerator
        if (K - 1 >= mod) A = 0;
        else {
            for (int i = 1; i <= K - 1; ++i) {
                A = A * (base - i) % mod;
            }
        }
        A = A * B % mod;
        B = B * quick_pow(base, K - 1) % mod; // denominator
        A = ((B - A) % mod + mod) % mod;
        printf("%I64d %I64d\n", A, B); 
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值