2014 BUPT 新生排位赛06

A  修路

链接:http://code.bupt.edu.cn/problem/p/447/

思路:就是裸的最小生成树,用邻接表会超内存,用prim我超时了,不知道是不是姿势不太好,又改成了并查集+kruskal 结果初始化函数里多打了一层for循环

附code以警示:

void init(int n)
{
    for(int i=0;i<n;i++){
        for(int i=0;i<n;i++){
            par[i]=i;
            high[i]=0;
        }
    }

导致本来半个小时就能过的题,卡到了快两个小时才过,哎。。。。。。

code:

#include <iostream>
#include <cstring>
#include <cstdlib>
#include<cstdio>
#include <algorithm>
#include <vector>
#include <cmath>
#include <queue>
#define INF 1000000000
#define eps 1e-9
using namespace std;
 
const int MAX_E=200005;
const int MAX_V=10005;
 
int par[MAX_V];
int high[MAX_V];
int t;
void init(int n)
{
    for(int i=0;i<n;i++){
            par[i]=i;
            high[i]=0;
    }
}
int Find(int x)
{
    if(par[x]==x) return x;
    else return par[x]=Find(par[x]);
}
void unite(int x,int y)
{
    x=Find(x);
    y=Find(y);
    if(x==y) return ;
    if(high[x]<high[y]) par[x]=y;
    else{
        par[y]=x;
        if(high[x]==high[y]) high[x]++;
    }
}
bool same(int x,int y)
{
    return Find(x)==Find(y);
}
 
struct edge{int u,v,cost;};
edge es[MAX_E];
bool cmp(const edge& e1,const edge& e2)
{
    return e1.cost<e2.cost;
}
 
int V,E;
 
int kruskal()
{
    int res=0;
    for(int i=0;i<t;i++){
        edge e=es[i];
        if(!same(e.u,e.v)){
            unite(e.u,e.v);
            res+=e.cost;
        }
    }
    return res;
}
 
int main()
{
    int ans;
    while(scanf("%d%d",&V,&E)!=EOF){
        t=0;
        init(V);
        for(int i=0;i<E;i++){
            int s,m,cc;
            scanf("%d%d%d",&s,&m,&cc);
            if(cc==0) unite(s,m);
            else{
                es[t].u=s;
                es[t].v=m;
                es[t++].cost=1;
            }
        }
       ans=kruskal();
       printf("%d\n",ans);
    }
    return 0;
}


B  高兴

链接:http://code.bupt.edu.cn/problem/p/445/

闲话:状态压缩dp,这一道应该让我引起足够大的重视,因为我以前是看过白书上的状态压缩dp的,但是做的时候根本没往这方面想(也可以说我把状压忘的一干二净了),我感觉对于每一个知识点,如果你不做一两道题,而仅仅是看一遍(看的时候还不一定都看懂了)你怎么可能能做出来呢。 在做这道题之前我又把状态压缩dp复习了一下,套用了我比赛时用的一个状态转移方程,wa了一次就过了。

思路: 

dp定义:S表示一个集合(选择了那些数字,如果了选择了其二进制位上为0否则为1),dp[S][i] 表示将集合S中的数字排列,并将第i个数字放在这个排列的最右边时的高兴值的最大值。

状态转移方程:dp[s][v]=max(dp[s][v],dp[s|1<<u][u]+d[u][v]); 其实就是枚举S中的每个数字将它放在v的左边,其中的最大值就是dp[s][v]的值

dp边界条件:我把他们全部都附成了0。

还有一些注意点我都注释在代码里面了。

code:

#include <iostream>
#include <cstring>
#include <cstdlib>
#include<cstdio>
#include <algorithm>
#include <vector>
#include <cmath>
#include <queue>
#define INF -1000000000
#define eps 1e-9
using namespace std;
 
const int MAX_N=20;
const int MAX_V=10005;
 
int dp[1<<MAX_N][MAX_N],d[MAX_N][MAX_N],n;
 
void solve()
{
    for(int s=0;s<1<<n;s++) for(int j=0;j<n;j++) dp[s][j]=0;
    dp[(1<<n)-1][0]=0;
    for(int s=(1<<n)-2;s>=0;s--){
        for(int v=0;v<n;v++){
            if(!(s>>v&1)) continue;
            for(int u=0;u<n;u++){
                if(!(s>>u&1)){
                    if(!dp[s][v]) dp[s][v]=dp[s|1<<u][u]+d[u][v];//注意此题d[i][j]的值可能是负数所以在0的时候不能直接更新 得先赋值
                    else dp[s][v]=max(dp[s][v],dp[s|1<<u][u]+d[u][v]);
                }
            }
        }
    }
    int res=dp[1][0];//此处同上
    for(int i=1;i<n;i++) res=max(res,dp[1<<i][i]);
    printf("%d\n",res);
}
int main()
{
    while(~scanf("%d",&n)){
        for(int i=0;i<n;i++) for(int j=0;j<n;j++) scanf("%d",&d[i][j]);
        solve();
    }
    return 0;
}

C 排序

链接:http://code.bupt.edu.cn/problem/p/449/

思路:看着比赛时的通过率,感觉自己是渣渣应该做不出来就没敢去尝试,之后做的还是挺顺的,就是根据n的

大小选择排序方法,因为数的大小是在0~10000之间所以可以用统计排序的方法,其复杂度是n(当n<10000的时候恒为10000)所以我们比较nlongn

和10000的大小发现当n小于1000的时候nlongn更快,其他时候用统排更快。

code:

#include <iostream>
#include <cstring>
#include <cstdlib>
#include<cstdio>
#include <algorithm>
#include <vector>
#include <cmath>
#include <queue>
#define INF 999999999
#define eps 1e-9
using namespace std;
 
const int MAX_M=200;
const int MAX_N=100005;
const int p=1000000009;
 
int nn[10005];
long long n;
int main()
{
    while(~scanf("%lld",&n)){
        if(n<=1000){
            for(long long i=0;i<n;i++) scanf("%d",&nn[i]);
            sort(nn,nn+n);
            for(int i=0;i<n;i++){
                printf("%d",nn[i]);
                if(i!=n-1) printf(" ");
            }
            printf("\n");
        }
        else{
            int maxnt=0;
            memset(nn,0,sizeof(nn));
            while(n--){
                int t;
                scanf("%d",&t);
                maxnt=max(maxnt,t);
                nn[t]++;
            }
            for(int i=0;i<=maxnt;i++){
                while(nn[i]!=0){
                    printf("%d",i);
                    nn[i]--;
                    if(nn[maxnt]) printf(" ");
                }
            }
            printf("\n");
        }
    }
}

爱好和平

链接:http://code.bupt.edu.cn/problem/p/444/

思路:就是树形dp,当时比赛的时候一直没发现这个图是树想了一个比较暴力的方法一直超时。 方法就是首先一遍dfs统计每个节点的深度,则每个节点被摧毁后的最大联盟值就是 子节点深度的最大值和总节点数减去此节点的深度 两者之间的最大值,再进行一遍dfs找出最小的最大联盟值就得到答案了。

code:

#include <iostream>
#include <cstring>
#include <cstdlib>
#include<cstdio>
#include <algorithm>
#include <vector>
#include <cmath>
#include <queue>
#define INF -1000000000
#define eps 1e-9
using namespace std;
 
const int MAX_E=400005;
const int MAX_V=100005;
 
struct edge{int to,next;} E[MAX_E];
int depth[MAX_V],cnt[MAX_V],n,m;
int head[MAX_V],si;
 
void add_edge(int s,int t)
{
    E[si].to=t;
    E[si].next=head[s];
    head[s]=si++;
}
int dfs(int v,int f)
{
    depth[v]=1;
    for(int i=head[v];i!=-1;i=E[i].next){
        int mid=E[i].to;
        if(mid==f) continue;
        depth[v]+=dfs(mid,v);
    }
    return depth[v];
}
void dfs2(int v,int f)
{
    for(int i=head[v];i!=-1;i=E[i].next){
        int mid=E[i].to;
        if(mid==f) continue;
        dfs2(mid,v);
        cnt[v]=max(cnt[v],depth[mid]);
    }
    if(v!=1) cnt[v]=max(cnt[v],depth[1]-depth[v]);
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF){
        memset(head,-1,sizeof(head));
        memset(cnt,0,sizeof(cnt));
        si=0;
        for(int i=0;i<m;i++){
            int s,t;
            scanf("%d%d",&s,&t);
            add_edge(s,t);
            add_edge(t,s);
        }
        dfs(1,0);
        dfs2(1,0);
        int nn=1;
        for(int i=2;i<=n;i++){
            if(cnt[i]<cnt[nn]) nn=i;
        }
        printf("%d\n",nn);
    }
}

E 还没有做待更新

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值