2015微软编程之美挑战赛初赛第2场

题目列表:http://hihocoder.com/contest/msbop2015round2b/problems

第一题:扑克牌。

题意:一副不含王的扑克牌由52张牌组成,由红桃、黑桃、梅花、方块4组牌组成,每组13张不同的面值。现在给定52张牌中的若干张,请计算将它们排成一列,相邻的牌面值不同的方案数。输出模2^64之后的值。牌的表示方法为XY,其中X为面值,为2、3、4、5、6、7、8、9、T、J、Q、K、A中的一个。Y为花色,为S、H、D、C中的一个。如2S、2H、TD等。

输入:第一行为一个整数T,为数据组数。之后每组数据占一行。这一行首先包含一个整数N,表示给定的牌的张数,接下来N个由空格分隔的字符串,每个字符串长度为2,表示一张牌。每组数据中的扑克牌各不相同。

思路:小数据搜索就能过,大数据(N<=52)需要用动态规划(记忆化搜索)。可以有两种思路。

1、四维dp。dp[a][b][c][d]表示用a个只出现1次的、b个出现2次的、c个出现3次的和d个出现4次的能够放置的方法数。每次枚举当前位放置的是出现几次(1~4)的,然后求和。对于当前放置出现1~4次的转移方程见代码。拿出现3次的来解释:当前位放置的方法数有3c个,这样放置会减少一个出现3次的,增加一个出现两次的,所以剩下的放置数是dp(a,b+1,c-1,d)。但是后者包括下一个位置放置的与当前位相同的情况,所以要减去这种情况。与当前位相同有两种选择,所以是2*dp(a+1,b,c-1,d)。但是一定注意,这样减又减多了,因为减去的部分包括下一位和下下位都相同的情况,而这种在dp(a,b+1,c-1,d)里是不会出现的,所以还得加回来。如此这般,转移方程就写好了。

2、dp[a][b][c][d][pre]表示用a个只出现1次的、b个出现2次的、c个出现3次的和d个出现4次的以及上一位是pre次能够放置的方法数。总体思路与1差不多,这样的好处是不用考虑加多减多。状态转移方程见代码。


注意:题目要求对答案模2^64,那么应该用unsigned long long 来存储(输出是%llu),而且相当于不做任何处理直接加即可,因为如果溢出就相当于模2^64运算。

思路1:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define MOD 1<<64
#define N 54
#define M 13
char ch[5];
int num[M+5],hh[1000];
int T,c,n;
unsigned long long dp[M+3][M+3][M+3][M+3];
void init(){
    int i;
    for(i = 2;i<=9;i++)
        hh['0'+i] = i;
    hh['T'] = 10;
    hh['J'] = 11;
    hh['Q'] = 12;
    hh['K'] = 13;
    hh['A'] = 1;
    memset(dp, -1, sizeof(dp));
    dp[0][0][0][0]= 1;
}
unsigned long long test(int a,int b,int c,int d){
    unsigned long long res = 0;
    if(dp[a][b][c][d] != -1)
        return dp[a][b][c][d];
    if(a > 0)           //放置出现一次的
        res += a*test(a-1,b,c,d);
    if(b > 0)           //放置出现两次的
        res += 2*b*(test(a+1, b-1, c, d) - test(a, b-1, c, d));
    if(c > 0)           //放置出现三次的
        res += 3*c*(test(a, b+1, c-1, d) - 2*(test(a+1,b,c-1,d) - test(a,b,c-1,d)));
    if(d > 0)           //放置出现四次的
        res += 4*d*(test(a, b, c+1, d-1) - 3*(test(a,b+1,c,d-1) - 2*(test(a+1,b,c,d-1) - test(a,b,c,d-1))));
    return dp[a][b][c][d] = res;
}
int main(){
    init();
    scanf("%d",&T);
    for(c = 1;c<=T;c++){
        int i,x,y,z,w;
        memset(num, 0, sizeof(num));
        scanf("%d",&n);
        for(i = 1;i<=n;i++){
            scanf("%s",ch);
            num[hh[ch[0]]]++;
        }
        x = y = z = w = 0;
        for(i = 1;i<=M;i++){
            if(num[i]==1)
                x++;
            else if(num[i] == 2)
                y++;
            else if(num[i] == 3)
                z++;
            else if(num[i] == 4)
                w++;
        }
        printf("Case #%d: %llu\n",c,test(x,y,z,w));
    }
    return 0;
}

思路2:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define MOD 1<<64
#define N 54
#define M 13
char ch[5];
int num[M+5],hh[1000];
int T,c,n;
unsigned long long dp[M+3][M+3][M+3][M+3][5];
void init(){
    int i;
    for(i = 2;i<=9;i++)
        hh['0'+i] = i;
    hh['T'] = 10;
    hh['J'] = 11;
    hh['Q'] = 12;
    hh['K'] = 13;
    hh['A'] = 1;
    memset(dp, -1, sizeof(dp));
    for(i = 0;i<5;i++)
        dp[0][0][0][0][i] = 1;
}
unsigned long long test(int a,int b,int c,int d,int pre){
    unsigned long long res = 0;
    if(dp[a][b][c][d][pre] != -1)
        return dp[a][b][c][d][pre];
    if(a > 0){
        if(pre != 1)
            res += a*test(a-1,b,c,d,0);
        else
            res += (a-1)*test(a-1,b,c,d,0);
    }
    if(b > 0){
        if(pre != 2)
            res += 2*b*(test(a+1, b-1, c, d ,1));
        else
            res += 2*(b-1)*(test(a+1, b-1, c, d ,1));
    }
    if(c > 0){
        if(pre != 3)
            res += 3*c*(test(a, b+1, c-1, d ,2));
        else
            res += 3*(c-1)*(test(a, b+1, c-1, d ,2));
    }
    if(d > 0){
        if(pre != 4)
            res += 4*d*(test(a, b, c+1, d-1 ,3));
        else
            res += 4*(d-1)*(test(a, b, c+1, d-1 ,3));
    }
    return dp[a][b][c][d][pre] = res;
}
int main(){
    init();
    scanf("%d",&T);
    for(c = 1;c<=T;c++){
        int i,x,y,z,w;
        memset(num, 0, sizeof(num));
        scanf("%d",&n);
        for(i = 1;i<=n;i++){
            scanf("%s",ch);
            num[hh[ch[0]]]++;
        }
        x = y = z = w = 0;
        for(i = 1;i<=M;i++){
            if(num[i]==1)
                x++;
            else if(num[i] == 2)
                y++;
            else if(num[i] == 3)
                z++;
            else if(num[i] == 4)
                w++;
        }
        printf("Case #%d: %llu\n",c,test(x,y,z,w,0));
    }
    return 0;
}

第二题:攻城略地。

题意:A、B两国间发生战争。已知A国共有n个城市(编号1, 2, …, n),城市间有一些道路相连。每座城市的防御力为w,直接攻下该城的代价是w。若该城市的相邻城市(有道路连接)中有一个已被占领,则攻下该城市的代价为0。除了占领城市,B国还要摧毁A国的交通系统,因而他们需要破坏至少k条道路。由于道路损毁,攻下所有城市的代价相应会增加。假设B国可以任意选择要摧毁的道路,那么攻下所有城市的最小代价是多少?

输入:第一行一个整数T,表示数据组数,以下是T组数据。每组数据第一行包含3个整数n, m, k。第二行是n个整数,分别表示占领城市1, 2, …, n的代价w。接下来m行每行两个数i, j,表示城市i与城市j间有一条道路。

思路:实际上是贪心的想法。首先考虑原图(不删除任何边),其最小代价是每个连通分支中的最小代价之和。下面考虑删边,如果可以继续删边而不增加连通分支数量,那么最小代价不会增加。直到将原图删成了一棵森林。如果还需要继续删边,那么必然会增加代价,而显然增加的量为没有计入总代价的代价最小点的代价(设为t点)。因为总可以在t所在的连通分支中将t与之前此连通分支内的最小代价点分开。如此删除直到删除符合题意的边为止。

一开始大数据没有过,因为我在读入数据后先排序,然后每找到一个连通分支的最小代价就标记相应位置的数已经选取。实际上这样的话如果原始的图没有边,那么复杂度变成了O(n^2),所以会TLE。实际上在深搜判连通的时候再产生t数组,最后排序扫一遍即可,复杂度O(nlogn)。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 1000005
#define INF 0x3fffffff
int c,T,n,m,q,w,len;
int s[N],t[N],first[N],flag[N],top;
struct edge{
    int y,next,w;
}e[N<<1];
long long res;
void add(int x,int y){
    e[top].y = y;
    e[top].next = first[x];
    first[x] = top++;
}
void dfs(int x){
    int i;
    flag[x] = 1;
    if(w>s[x]){                 //此时再生成t数组
        if(w != INF)
            t[len++] = w;
        w = s[x];
    }else
        t[len++] = s[x];
    for(i = first[x];i!=-1;i=e[i].next)
        if(-1 == flag[e[i].y])
            dfs(e[i].y);
}
int main(){
    scanf("%d",&T);
    for(c = 1;c<=T;c++){
        int i,j,a,b,con=0;
        res = len = 0;
        memset(flag, -1, sizeof(flag));
        memset(first, -1, sizeof(first));
        top = 0;
        scanf("%d %d %d",&n,&m,&q);
        for(i = 1;i<=n;i++)
            scanf("%d",&s[i]);
        for(i = 1;i<=m;i++){
            scanf("%d %d",&a,&b);
            add(a,b);
            add(b,a);
        }
        for(i = 1,con=0;i<=n;i++)
            if(flag[i] == -1){
                w = INF;
                dfs(i);
                res += w;       //w求得为每个连通分支的最小代价
                con ++;         //连通分支数
            }
        sort(t,t+len);
        j = n-con-(m-q);        //j是将原图删成森林之后还需要删除的边数
        for(i = 0;j>0;j--,i++)
            res += t[i];
        printf("Case #%d: %lld\n",c,res);
    }
    return 0;
}

第三题:八卦的小冰。

题意:某社交网站中有N个用户,用户和用户之间有亲密度。亲密度非负,若大于零表示这两个用户之间是好友关系。小冰会不停地更新用户之间的亲密度,同时,小冰也有可能会改变对一个用户性别的判断。小冰想知道这个社交网络的八卦度是多少。八卦度的定义是社交网络中所有异性好友之间的亲密度之和。对于每一个询问,输出一行包含询问的八卦度。

输入:第一行一个整数T,表示数据组数。接下来是T组数据,每组数据的格式如下:第一行是三个整数N, M, Q,分别表示用户数、初始的好友对数、操作数。
第二行是N个空格隔开的数,第i个数表示i号用户的性别,用0或1表示。
接下来的M行,每行三个数x, y, z,代表初始状态用户x和用户y之间的亲密度是z。除此之外的用户之间的亲密度初始为0。
接下来是Q行,每行是以下三种操作中的一种:
1. “1 x”:改变用户x的性别
2. “2 x y z”:改变用户x与用户y之间的亲密度为z
3. “3”:询问八卦度

思路:最原始的思路就是模拟,但着实没想到模拟就能过大数据。因为大数据范围是:1 ≤ N, M, Q ≤ 100000,那么如果最后每两个人之间都有亲密度,那么2*100000^2这个量级的边没法存。看了下别人的AC代码也基本都是模拟过的。其实这题的数据可能就是询问八卦度(操作3)的数量比较多,所以应该维护结果的值而不是每次询问现计算。初次之外没有什么可注意的。

#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 100005
int c,T,n,m,q;
int s[N],first[N],top;
struct edge{
    int y,next,w;
}e[N<<5];
long long res;
void add(int x,int y,int w){
    e[top].y = y;
    e[top].w = w;
    e[top].next = first[x];
    first[x] = top++;
}
void update(int x){
    int i,y;
    for(i = first[x];i!=-1;i=e[i].next){
        y = e[i].y;
        if(s[x] == s[y])
            res -= e[i].w;
        else
            res += e[i].w;
    }
}
int change(int x,int y,int w){
    int i,tmp = -1;
    for(i = first[x];i!=-1;i=e[i].next){
        if(e[i].y == y){
            tmp = e[i].w;
            e[i].w = w;
            return tmp;
        }
    }
    add(x,y,w);
    return 0;
}
int main(){
    scanf("%d",&T);
    for(c = 1;c<=T;c++){
        int i,j,a,b,w,op;
        res = 0;
        memset(first, -1, sizeof(first));
        top = 0;
        scanf("%d %d %d",&n,&m,&q);
        for(i = 1;i<=n;i++)
            scanf("%d",&s[i]);
        for(i = 1;i<=m;i++){
            scanf("%d %d %d",&a,&b,&w);
            add(a,b,w);
            add(b,a,w);
            if(s[a] != s[b])
                res += w;
        }
        printf("Case #%d:\n",c);
        while(q--){
            scanf("%d",&op);
            if(op == 1){
                scanf("%d",&a);
                s[a] = 1-s[a];
                update(a);
            }else if(op == 2){
                scanf("%d %d %d",&a,&b,&w);
                j = change(a,b,w);
                change(b,a,w);
                if(s[a] != s[b]){
                    res -= j;
                    res += w;
                }
            }else
                printf("%lld\n",res);
        }
    }
    return 0;
}


  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值