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


题目链接:http://hihocoder.com/contest/msbop2015round2a/problems

第一题:彩色的树</p><p>题意:给定一棵n个节点的树,节点编号为1, 2, …, n。这是一棵彩色的树,每个节点恰好可以染一种颜色。初始时,所有节点的颜色都为0。现在需要实现两种操作:1. 改变节点x的颜色为y;2. 询问整棵树被划分成了多少棵颜色相同的子树。即每棵子树内的节点颜色都相同,而相邻子树的颜色不同。</p><p>输入:第一行一个整数T,表示数据组数,以下是T组数据。每组数据第一行是n,表示树的节点个数。接下来n - 1行每行两个数i和j,表示节点i和j间有一条边。接下来是一个数q,表示操作数。之后q行,每行表示以下两种操作之一:1. 若为"1",则询问划分的子树个数。2. 若为"2 x y",则将节点x的颜色改为y。</p><p>思路:一开始的思路是用一个数组表示颜色,每次更新通过遍历变换颜色点的邻接点来更新子树值,但是会TLE。实际上如果用stl的map来多记录一些信息,那么每次更新的时间是较小的(map的红黑树)。map[i][j]保存的内容是i结点的儿子中颜色为j的个数。维护过程见代码。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
using namespace std;
#define N 100005
int s[N];
struct edge{
    int y,next;
}e[N<<1];
int first[N],top,f[N];
int T,c,n,q,op,res;
map<int,int> mm[N];
void add(int x,int y){
    e[top].y = y;
    e[top].next = first[x];
    first[x] = top++;
}
void dfs(int x,int fa){
    int i,y;
    f[x] = fa;
    for(i = first[x];i!=-1;i=e[i].next){
        y = e[i].y;
        if(y == fa)
            continue;
        dfs(y,x);
        mm[x][0]++;
    }
}
void update(int a,int b){
    if(mm[a].find(s[a]) != mm[a].end()) //如果儿子中有颜色和当前结点a相同的,那么是变换后多出来的子树
        res += mm[a][s[a]];
    if(mm[a].find(b) != mm[a].end())    //如果儿子中有颜色和变换后的颜色b相同的,那么是变换后需要减少的子树
        res -= mm[a][b];
    if(f[a]){                           //如果有父节点
        if(s[f[a]] == s[a])
            res++;
        if(s[f[a]] == b)
            res--;
        mm[f[a]][b]++;                  //更新父节点的子节点颜色值
        mm[f[a]][s[a]]--;
    }
    s[a] = b;
}
int main(){
    scanf("%d",&T);
    for(c = 1;c<=T;c++){
        int i,a,b;
        scanf("%d",&n);
        for(i = 1;i<=n;i++)
            mm[i].clear();
        memset(s, 0, sizeof(s));
        memset(first, -1, sizeof(first));
        top = 0;
        res = 1;
        for(i = 1;i<n;i++){
            scanf("%d %d",&a,&b);
            add(a,b);
            add(b,a);
        }
        printf("Case #%d:\n",c);
        dfs(1,0);
        scanf("%d",&q);
        while(q--){
            scanf("%d",&op);
            if(op == 1)
                printf("%d\n",res);
            else{
                scanf("%d %d",&a,&b);
                update(a,b);
            }
        }
    }
    return 0;
}

第二题:建造金字塔

题意:金字塔是一个底边在x轴上的等腰直角三角形。你是二次元世界的一个建筑承包商。现在有N个建造订单,每个订单有一个收益w,即建造此金字塔可获得w的收益。对每个订单可以选择建造或不建造。建造一个金字塔的成本是金字塔的面积,如果两个或多个金字塔有重叠面积,则建造这些金字塔时重叠部份仅需建造一次。建造一组金字塔的总利润是收益总和扣除成本。现给出这些订单,请求出最大利润。

输入:输入数据第一行为一个整数T,表示数据组数。每组数据第一行为一个整数N,表示订单数目。接下来N行,每行三个整数x, y, w,表示一个订单。(x, y)表示建造出的金字塔的顶点,w表示收益。

思路:动态规划。首先可以知道,我们只需要关心每个三角形的右边界点即可。这样才能包含一个完整的三角形。因此,对于所有的三角形,用状态(l,r,w)来描述它。
接下来,定义d(j)表示区间[0,j]上收益的最大值。事先我们要统计出最大边界lim,这样j的变化范围就是0≤j≤lim。下面考虑如何寻找状态转移方程。
(1)当j≥x[i].r时,表示第i个三角形完全包括在[0,j]之间,取建造它与不建造它收益的较大者。即d(j)=max{d(j),d(j)+x[i].w};
(2)当不满足(1)时但j≥x[i].l时,我们关注的是x[i].r处的收益最大值,画图后容易知道,收益增加值是建造第i个金字塔的收益w减去多建设的面积,即S(x[i].l,x[i].r)-S(x[i].l,j)。即得到如下状态转移方程:d(x[i].r)=max{d(x[i].r),d(j)+x[i].w-S(x[i].l,x[i].r)+S(x[i].l,x[i].r)};
(3)当前两个均不满足时,状态转移方程其实和(2)类似,即d(x[i].r)=max{d(x[i].r),d(j)+x[i].w-S(x[i].l,x[i].r)};
最后,不要忘记一种特殊情况:只建造第i个金字塔时候的收益,因此最后还要取上述计算出的收益和只建造第i个金字塔收益的较大者。
当所有区间的收益最大值计算完毕后,答案就是他们中的最大值。注意事先要把d(j)都初始化为无穷小,表示没有计算过。(参考了http://www.bkjia.com/ASPjc/990122.html)

感悟:动态规划作为算法中最重要的部分,可谓十分灵活。这次比赛从资格赛到两场初赛都有动态规划的题目,但是类型均不同。所以还是要经常总结并且灵活应用动态规划。

#include <cstdio>
#include <cstring>
#include <algorithm>
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define N 1005
using namespace std;
struct node{
    int x,y,w;
}p[N];
double dp[N<<1];
int T,n,m,c;
int cmp(node a,node b){
    return a.x<b.x;
}
double area(int x,int y){
    return (y-x)*(y-x)/4.0;
}
int main(){
    scanf("%d",&T);
    for(c = 1;c<=T;c++){
        int i,j,a,b;
        double res = 0;
        scanf("%d",&n);
        for(i = 1,m=0;i<=n;i++){
            scanf("%d %d %d",&a,&b,&p[i].w);
            p[i].x = a-b;
            p[i].y = a+b;
            m = max(m,a+b);
        }
        sort(p+1,p+1+n,cmp);
        for(i = 0;i<=m;i++)
            dp[i] = -0x3fffffff;
        for(i = 1;i<=n;i++){
            for(j = m;j>=0;j--){
                if(j>=p[i].y)
                    dp[j] = max(dp[j], dp[j]+p[i].w);
                else if(j>p[i].x)
                    dp[p[i].y] = max(dp[p[i].y], dp[j]+p[i].w-area(p[i].x, p[i].y)+area(p[i].x, j));
                else
                    dp[p[i].y] = max(dp[p[i].y], dp[j]+p[i].w-area(p[i].x,p[i].y));
            }
            dp[p[i].y] = max(dp[p[i].y] , p[i].w-area(p[i].x,p[i].y));
        }
        for(i = 0;i<=m;i++)
            res = max(res, dp[i]);
        
        printf("Case #%d: %.2lf\n",c,res);
    }
    return 0;
}

第三题:质数相关

题意:两个数a和 b (a<b)被称为质数相关,是指a × p = b,这里p是一个质数。一个集合S被称为质数相关,是指S中存在两个质数相关的数,否则称S为质数无关。如{2, 8, 17}质数无关,但{2, 8, 16}, {3, 6}质数相关。现在给定一个集合S,问S的所有质数无关子集中,最大的子集的大小。

输入:第一行为一个数T,为数据组数。之后每组数据包含两行。第一行为N,为集合S的大小。第二行为N个整数,表示集合内的数。

思路:如果将集合内的所有数当做定点,两个数如果质数相关则添边,那么原题转化为求图的最大独立集。如果图是任意图,那么这个NPC问题无法在多项式时间内求出,但是仔细分析可知这个图不可能存在圈,所以必然是一个二部图。接下来进行二部图染色即可,选取每个连通分支中颜色多的那种进行求和即可。

其他的,用求最大匹配的方法亦可(为什么定点只需1~n就可以?不用分出两部来吗)。

#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
#define N 1005
#define M 500000
int n,c,T;
int s[N];
int first[N<<1],top,flag[M+5],used[N];
struct edge{
    int y,next;
}e[N*N/2];
int col[N][2],len,res;
void prime(){                       //筛质数,质数为0,合数为1
    int i,j;
    flag[1] = 1;
    for(i = 2;i<M;i++)
        if(!flag[i])
            for(j = i<<1;j<=M;j+=i)
                flag[j] = 1;
}
void add(int x,int y){
    e[top].y = y;
    e[top].next = first[x];
    first[x] = top++;
}
void dfs(int x,int c){
    int i;
    used[x] = 1;
    col[len][c]++;
    for(i = first[x];i!=-1;i=e[i].next)
        if(!used[e[i].y])
            dfs(e[i].y, 1-c);
}
int main(){
    prime();
    scanf("%d",&T);
    for(c = 1;c<=T;c++){
        int i,j;
        top = len = res = 0;
        memset(used, 0, sizeof(used));
        memset(first, -1, sizeof(first));
        memset(col, 0, sizeof(col));
        scanf("%d",&n);
        for(i = 1;i<=n;i++)
            scanf("%d",&s[i]);
        sort(s+1,s+n+1);
        for(i = 1;i<n;i++)
            for(j = i+1;j<=n;j++){
                if(s[j]%s[i]==0 && !flag[s[j]/s[i]])
                    add(i,j),add(j,i);
            }
        for(i = 1;i<=n;i++){
            if(!used[i])
                dfs(i,0);
            len++;
        }
        for(i = 0;i<len;i++)
            res += max(col[i][0],col[i][1]);
        printf("Case #%d: %d\n",c,res);
    }
    return 0;
}

通过最大匹配求最大独立集:

#include <cstring>
#include <algorithm>
using namespace std;
#define N 1005
int n,c,T;
int s[N];
int first[N<<1],top,used[N],link[N];
struct edge{
    int y,next;
}e[N*N/2];
int isprime(int x){
    int i;
    if(x==2)
        return 1;
    for(i = 2;i*i<=x;i++)
        if(x % i == 0)
            return 0;
    return 1;
}
void add(int x,int y){
    e[top].y = y;
    e[top].next = first[x];
    first[x] = top++;
}
int dfs(int i){
    int j,y;
    for(j = first[i];j!=-1;j=e[j].next){
        y = e[j].y;
        if(!used[y]){
            used[y] = 1;
            if(link[y] == -1||dfs(link[y])){
                link[y] = i;
                return 1;
            }
        }
    }
    return 0;
}
int hungary(){
    int i,res = 0;
    for(i = 1;i<=n;i++){
        memset(used, 0, sizeof(used));
        if(dfs(i))
            res++;
    }
    return res;
}
int main(){
    scanf("%d",&T);
    for(c = 1;c<=T;c++){
        int i,j;
        memset(first, -1, sizeof(first));
        memset(link, -1, sizeof(link));
        top = 0;
        scanf("%d",&n);
        for(i = 1;i<=n;i++)
            scanf("%d",&s[i]);
        sort(s+1,s+n+1);
        for(i = 1;i<n;i++)
            for(j = i+1;j<=n;j++)
                if(s[j]%s[i]==0 && isprime(s[j]/s[i]))
                    add(i,j),add(j,i);
        printf("Case #%d: %d\n",c,n-hungary()/2);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值