2014 Multi-University Training Contest 1 题解

待填坑

A. Couple Doubi

  这道题虽然看起来很变态,但是其实是一道大水题。我们来推导一下式子。首先我们要知道两个性质:
  性质1:质数一定有原根。
  性质2:若 g m的原根, g0,g1,...,gϕ(m)1 组成 m 的一组既约剩余系。
  对于这道题,我们任取p的一个原根 g ,由以上性质可知,p1i=1inp1i=1gni(mod m)
  当 ip1 时, gi≢1 ,由等比数列求和公式得到,上式 gpigigi10(mod m)
  当 i=p1 时,上式 p1
  这样答案就很简单了,就是 n/(p1) 为奇数时赢,否则不赢。复杂度 O(1)

#include<bits/stdc++.h>
int k, p;

int main()
{
    while (~scanf("%d%d", &k, &p))
        if ((k / (p - 1)) & 1)
            printf("YES\n");
        else
            printf("NO\n");
    return 0;
}

B. Jump

  这是一道最小费用最大流的题,建图方法如下:
  每个格子在 X 部和Y部中分别对应一个点,从源点向 X 部中的每一个点连一条边,流量1,费用0;从Y部中的每一个点向汇点连边,流量1,费用0;若某个格子可以走到另一个格子,从 X 部对应的起点向Y部对应的终点连边,流量1,费用是花费的能量减赚得的能量。最后再新增一个点,从源点向这个点连边,流量 k ,费用0;从这个点向Y部每个点连边,流量1,费用0。建好图后跑一遍最小费用最大流,如果不满流即不可行。

#include<bits/stdc++.h>

#define MAXN (300)
#define MAXM (5010)
#define MAXS (20)
const int INF = INT_MAX;

struct node{
    int v, c, f, w, nxt;
}edge[MAXM];

int T, n, m, k;
int head[MAXN], edge_cnt;
int dis[MAXN];
bool vis[MAXN];
int pre[MAXN];
char str[MAXS][MAXS];

bool spfa(int s, int t, int &flow, int &cost){
    std::queue <int> que;
    for (int i = 0; i < MAXN; ++ i)
        dis[i] = INF;
    memset(vis, false, sizeof(vis));
    dis[s] = 0;
    vis[s] = true;
    que.push(s);
    while (!que.empty()){
        int u = que.front();
        que.pop();
        vis[u] = false;
        //std::cout << "f**k" << std::endl;
        for (int i = head[u]; i + 1; i = edge[i].nxt){
            int v = edge[i].v, c = edge[i].c, f = edge[i].f, w = edge[i].w;
            if (c - f > 0 && dis[v] > dis[u] + w){
                dis[v] = dis[u] + w;
                pre[v] = i;
                if (!vis[v]){
                    vis[v] = true;
                    que.push(v);
                }
            }
        }
    }
    //std::cout << "f**k" << std::endl;
    if (dis[t] == INF)
        return false;
    int nowflow = INF;
    for (int u = t; u != s; u = edge[pre[u] ^ 1].v)
        nowflow = std::min(nowflow, edge[pre[u]].c - edge[pre[u]].f);
    flow += nowflow;
    cost += dis[t] * nowflow;
    for (int u = t; u != s; u = edge[pre[u] ^ 1].v){
        edge[pre[u]].f += nowflow;
        edge[pre[u] ^ 1].f -= nowflow;
    }
    return true;
}

int mincost(int s, int t, int limit){
    int flow = 0, cost = 0;
    while (spfa(s, t, flow, cost));
    if (flow < limit)
        return 1;
    return cost;
}

void addedge(int u, int v, int c, int w){
    edge[++ edge_cnt] = {v, c, 0, w, head[u]};
    head[u] = edge_cnt;
    edge[++ edge_cnt] = {u, 0, 0, -w, head[v]};
    head[v] = edge_cnt;
}

int main(){
    scanf("%d", &T);
    for (int kk = 1; kk <= T; ++ kk){
        scanf("%d%d%d", &n, &m, &k);
        memset(head, -1, sizeof(head));
        edge_cnt = -1;
        getchar();
        for (int i = 0; i < n; ++ i){
            for (int j = 1; j <= m; ++ j)
                str[i][j] = getchar();
            getchar();
        }
        int s = 2 * n * m + 1, t = s + 1, ext = t + 1;
        for (int i = 1; i <= n * m; ++ i){
            addedge(s, i, 1, 0);
            addedge(i + n * m, t, 1, 0);
        }
        addedge(s, ext, k, 0);
        for (int i = 1; i <= n * m; ++ i)
            addedge(ext, i + n * m, 1, 0);
        for (int i = 0; i < n; ++ i)
            for (int j = 1; j <= m; ++ j){
                for (int ii = i + 1; ii < n; ++ ii){
                    int cost = ii - i - 1;
                    if (str[ii][j] == str[i][j])
                        cost -= str[i][j] - '0';
                    addedge(i * m + j, n * m + ii * m + j, 1, cost);
                }
                for (int jj = j + 1; jj <= m; ++ jj){
                    int cost = jj - j - 1;
                    if (str[i][jj] == str[i][j])
                        cost -= str[i][j] - '0';
                    addedge(i * m + j, n * m + i * m + jj, 1, cost);
                }
            }
        printf("Case %d : %d\n", kk, -mincost(s, t, m * n));
    }
    return 0;
}

E. Peter’s Hobby

  这是一道概率论加 DP 的题。状态转移方程如下:设 dp[i][j] 表示第 i 天天气为j的最大概率,则
   dp[i][j]=max0k<3dp[i1][k]×prob2[k][j]×prob2[j][hum[i]] 。复杂度 O(50×32)

#include<bits/stdc++.h>
using namespace std;

#define MAXN (60)

double prob1[3][4] = {0.6, 0.2, 0.15, 0.05, 0.25, 0.3, 0.2, 0.25, 0.05, 0.10, 0.35, 0.50};
double prob2[3][3] = {0.5, 0.375, 0.125, 0.25, 0.125, 0.625, 0.25, 0.375, 0.375};
double dp[MAXN][3];
int pre[MAXN][3], a[MAXN];
vector <int> ans;

int main(){
    for (int i = 0; i < 3; ++ i)
        for (int j = 0; j < 4; ++ j)
            prob1[i][j] = log(prob1[i][j]);
    for (int i = 0; i < 3; ++ i)
        for (int j = 0; j < 3; ++ j)
            prob2[i][j] = log(prob2[i][j]);
    int t;
    scanf("%d", &t);
    for (int kk = 1; kk <= t; ++ kk){
        for (int i = 0; i < MAXN; ++ i)
            for (int j = 0; j < 3; ++ j)
                dp[i][j] = -1e20; 
        int n;
        scanf("%d", &n);
        for (int i = 0; i < n; ++ i){
            char s[10];
            scanf("%s", s);
            int len = strlen(s);
            switch (len){
                case 3: a[i] = 0; break;
                case 6: a[i] = 1; break;
                case 4: a[i] = 2; break;
                case 5: a[i] = 3; break;
            }
        }
        dp[0][0] = log(0.63) + prob1[0][a[0]];
        dp[0][1] = log(0.17) + prob1[1][a[0]];
        dp[0][2] = log(0.20) + prob1[2][a[0]];
        for (int i = 1; i < n; ++ i)
            for (int j = 0; j < 3; ++ j)
                for (int k = 0; k < 3; ++ k)
                    if (dp[i - 1][j] + prob2[j][k] + prob1[k][a[i]] > dp[i][k]){
                        dp[i][k] = dp[i - 1][j] + prob2[j][k] + prob1[k][a[i]];
                        pre[i][k] = j;
                    }
        double maxi = -1e20;
        int sit;
        for (int i = 0; i < 3; ++ i)
            if (maxi < dp[n - 1][i]){
                maxi = dp[n - 1][i];
                sit = i;
            }
        ans.clear();
        for (int i = n - 1; i >= 0; -- i){
            ans.push_back(sit);
            sit = pre[i][sit];
        }
        printf("Case #%d:\n", kk);
        for (int i = ans.size() - 1; i >= 0; -- i){
            switch (ans[i]){
                case 0: printf("Sunny\n"); break;
                case 1: printf("Cloudy\n"); break;
                case 2: printf("Rainy\n"); break;
            }
        }
    }
    return 0;
}

I. Turn the Pokers

  观察容易发现这样三个性质:
  性质1:最后状态的0,1个数确定后,它们的任意排列都能得到。
  性质2:最后状态能取到的1的个数的奇偶性完全相同。
  性质3:在满足2的条件下,最后状态能取到的1的个数的最大值和最小值之间的任意数都能取到。
  因此,我们用 l r来记录最大和最小值,每读入一个 xi ,我们就用它来更新 l r
  如果 r+xim ,那么 r=r+xi ;如果 l+xim) ,那么 r=2mxl ;否则 r=m((mxir)&1
  如果 lxi ,那么 l=lxi ;如果 rxi ,那么 l=xir ;否则 l=(xir)&1
  复杂度 O(n)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100010;
const int moder = 1e9 + 9;

int n, m, fac[MAXN], inv[MAXN], comb[MAXN];

int power(int num, int index){
    int ans = 1;
    while (index){
        if (index & 1)
            ans = 1ll * ans * num % moder;
        num = 1ll * num * num % moder;
        index >>= 1;
    }
    return ans;
}

int main(){
    fac[0] = inv[0] = 1;
    for (int i = 1; i < MAXN; ++ i)
        fac[i] = 1ll * i * fac[i - 1] % moder;
    for (int i = 1; i < MAXN; ++ i)
        inv[i] = power(fac[i], moder - 2);
    while (~scanf("%d%d", &n, &m)){
        int low = 0, high = 0, l, h;
        for (int i = 0; i <= m; ++ i)
            comb[i] = 1ll * fac[m] * inv[i] % moder * inv[m - i] % moder;
        for (int i = 0, x; i < n; ++ i){
            scanf("%d", &x);
            if (high + x <= m)
                h = high + x;
            else if (low + x >= m)
                h = 2 * m - x - low;
            else
                h = m - ((m ^ x ^ high) & 1);
            if (low >= x)
                l = low - x;
            else if (high <= x)
                l = x - high;
            else
                l = (x ^ high) & 1;
            high = h;
            low = l;
        }
        int cnt = 0;
        for (int i = low; i <= high; i += 2)
            cnt = (cnt + comb[i]) % moder;
        printf("%d\n", cnt);
    }
    return 0;
}

J. Rating

  一道概率论的题。由于分数只能是50的倍数,我们把分数除以50。先考虑只有一个账号的情况:设 xi 表示当前分数为 i ,到20分所需的期望。当2x19时,递推式为 xi=pxi+1+(1p)xi2+1 ,其它情况分类讨论一下即可。由于小女孩每次用分数更低的账号做题,显然最后的分数一定是 (19,20) (20,19) 。那么最后的答案就是 2x0x19
  方法一:用高斯消元法解上面得到的方程组。复杂度 O(n3)

#include<bits/stdc++.h>

# define MAXN (25)
const double eps = 1e-12;

double p, a[MAXN][MAXN];

void gauss_jordan(int n){
    for (int i = 0; i < n; ++ i){
        int r = i;
        for (int j = i + 1; j < n; ++ j){
            if (fabs(a[j][i]) - fabs(a[r][i]) > eps){
                r = j;
            }
        }
        if (r != i){
            for (int j = i; j <= n; ++ j){
                std::swap(a[i][j], a[r][j]);
            }
        }
        for (int k = 0; k < n; ++ k){
            if (k == i){
                continue;
            }
            for (int j = n; j >= i; -- j){
                a[k][j] -= a[k][i] / a[i][i] * a[i][j];
            }
        }
    }
}

int main(){
    while (~scanf("%lf", &p)){
        for (int i = 2; i <= 19; ++ i){
            a[i][i] = 1;
            a[i][i + 1] = -p;
            a[i][i - 2] = p - 1;
            a[i][20] = 1;
        }
        a[0][0] = p;
        a[0][1] = -p;
        a[0][20] = 1;
        a[1][1] = 1;
        a[1][2] = -p;
        a[1][0] = p - 1;
        a[1][20] = 1;
        gauss_jordan(20);
        printf("%lf\n", 2 * a[0][20] / a[0][0] - a[19][20] / a[19][19]);
    }
    return 0;
}

  方法二:我们令 ti=x0xi ,那么 ti 就表示从0分到 i 分的期望。显然有t0=0 t1=1p , t2=1p+1p2 。根据上面的式子易得到递推公式: ti=1p+ti1p1ppti3(i3) 。最终所求的答案就是 t19+t20 。复杂度 O(n)

#include<bits/stdc++.h>

#define MAXN (25)
const double eps = 1e-12;

double p, a[MAXN];

int main(){
    while(~scanf("%lf", &p)){
        a[0] = 0;
        a[1] = 1 / p;
        a[2] = 1 / p + 1 / p / p;
        for (int i = 3; i <= 20; ++ i){
            a[i] = 1 / p + a[i - 1] / p - (1 - p) * a[i - 3] / p;
        }
        printf("%lf\n", a[19] + a[20]);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值