zoukankan      html  css  js  c++  java
  • Codeforces Round #544 (Div. 3) 解题报告

    A. Middle of the Contest

    题目大意:给你(HH,MM)形式的两个时间点,它们之间相差偶数分钟,问中间时刻是多少?

    题目思路:计算两个时间点间有多少分钟,除2后加到起始时间上,注意分钟达到60时,时钟需要进位。

    AC代码:

    #include<bits/stdc++.h>
    using namespace std;
    int main()
    {
        int h1,h2,m1,m2,h3,m3;
        scanf("%d:%d",&h1,&m1);
        scanf("%d:%d",&h2,&m2);
        int tot=(h2-h1)*60+(m2-m1);
        tot/=2;
        h3=h1+tot/60;
        m3=m1+tot%60;
        h3+=m3/60;
        m3%=60;
        if(h3<10) printf("0%d:",h3);
        else printf("%d:",h3);
         if(m3<10) printf("0%d
    ",m3);
        else printf("%d
    ",m3);
        return 0;
     } 

    B. Preparation for International Women's Day

    题目大意:告诉你n个礼物,每个礼物中有di个糖果,礼物两两组合为一个盒子。要求盒子内糖果数为k的倍数。问最多可以装多少个符合条件的盒子?

    题目思路:直接枚举明显超时O(n2),可以考虑先将每个礼物对k取余,再记录取余得到的结果的出现次数,最后枚举一遍,计算拼凑出和为k的情况数。注意单独计算取余结果为0的礼物自己互相组合形成盒子。

    AC代码:

    #include<bits/stdc++.h>
    using namespace std;
    int t[105];
    bool vis[105];
    int main()
    {
        int n,k;
        cin>>n>>k;
        memset(t,0,sizeof(t));
        int tmp;
        for(int i=1;i<=n;i++)
        {
            cin>>tmp;
            t[tmp%k]++;
        }
        int ans=0;
        ans+=t[0]/2;
        vis[0]=true;
        for(int i=1;i<k;i++)
        {
            if(vis[i]) continue;
            if(k-i==i) ans+=t[i]/2;
            else ans+=min(t[i],t[k-i]);
            vis[i]=true;
            vis[k-i]=true;
        }
        cout<<ans*2<<endl;
        return 0;
     } 

    C. Balanced Team

    题目大意:告诉你n个数,定义一个合法的集合为:集合中元素的最大值与最小值的差值不超过5,问由这n个数形成的合法集合的大小最大为多少?

    题目思路:先将n个数排序,再枚举所有元素,维护一个最大值与最小值之差不超过5的队列,更新队列长度最大值。O(n*log(n))

    AC代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=200005;
    #define ll long long
    queue<ll> que;
    ll a[maxn];
    int main()
    {
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            scanf("%lld",&a[i]);
        sort(a+1,a+n+1);
        int ans=0;
        for(int i=1;i<=n;i++)
        {
            while(que.size()&&a[i]-que.front()>5) que.pop();
            que.push(a[i]);
            if(ans<que.size()) ans=que.size();
        } 
        cout<<ans<<endl;
        return 0;
     } 

    D. Zero Quantity Maximization

    题目大意:有两个集合,分别为A和B,其中各有n个元素。现在定义C集合:其中每个元素ci=d*ai+bi,d可取任意值,问当d取最优时,C集合中0元素最多为多少?

    题目思路:令(ai,bi)为一数对,数对有四种类型:①.(0,0)②(0,y)③(x,0)④(x,y)   

    对于第一种类型,d取任意值,对应的ci都是0,所以计算它的数量,加到最终答案里。

    对于第二种类型,d取任意值,对应的ci都不可能是0,所以忽略它的存在。

    对于第三种类型,只有d取0时,对应的ci才是0,所以对这一类单独计数,更新ans

    对于第四种类型,利用gcd把它化成最简形式(x,y互质),用STL中的map记录不同最简数对的数量,更新ans

    AC代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=200005;
    int a[maxn],b[maxn];
    struct node{
        int a,b;
        node(){}
        node(int a,int b):a(a),b(b){}
        bool operator<(const node&other) const{
            if(a==other.a) return b<other.b;
            return a<other.a;
        }
    };
    map<node,int> m1;
    int gcd(int a,int b) {
        return b==0?a:gcd(b,a%b);
    }
    int main()
    {
        int n,ans=0;
        scanf("%d",&n); 
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        for(int i=1;i<=n;i++)
            scanf("%d",&b[i]);
        int x=0,add=0;
        for(int i=1;i<=n;i++)
        {
            if(a[i]==0&&b[i]==0) add++;
            else if(a[i]==0) continue;
            else if(b[i]==0) x++,ans=max(ans,x);
            else 
            {
                int k=gcd(a[i],b[i]);
                int tmp=++m1[node(a[i]/k,b[i]/k)];
                ans=max(ans,tmp);
            }
        }
        printf("%d
    ",ans+add);
        return 0;
    } 

    比赛时做这道题比较仓促,没有分清楚4个情况,并且对于第四类数对没用到gcd以及map来计数,而是保存ai/bi的结果,来数相同结果的最多有多少。这种方法对精度要求很高,D题的第37个数据点为: 

    2

    999999999 1000000000

    999999998 999999999

    精度不够导致认为这两个结果相同,输出2,而不是1。所以还是用map吧....

    --------------------------------

    map的关键字key可以用pair<int,int>,具体操作就是p=make_pair(a[i]/k,b[i]/k);

    要比结构体实现方便一丢丢。

    E. K Balanced Teams

    题目大意:C题的升级版,背景描述差不多。告诉你n个数,定义一个合法的集合为:集合中元素的最大值与最小值的差值不超过5,问的是找出不超过k个合法集合,它们的并集中的元素个数最大是多少?

    题目思路:先对所有元素从小到大排序,问题可以重述为,在[1,n]范围内选取不相交的k个合法集合,使元素个数和最大。(显然,符合题意的k个集合的交集为空集,这样能保证并集元素个数最大。)

    考虑第n个元素,它可以包含在最终答案的k个合法集合当中,也可以不包含在其中。

    ①在k个集合中:则其中1个合法集合已经找到,就是n所在的合法集合,然后从后往前数,找到第一个不在n所属集合中的元素位置pos,剩下要解决的问题就变成了在[1,pos]范围内选取不相交的k-1个合法集合,使元素个数和最大

    ②不在k个集合中:要解决的问题就变成了在[1,n-1]范围内选取选取不相交的k个合法集合,使元素个数和最大

    这是一个动态规划问题,根据上述分析,容易定义状态并写出状态转移方程式: 

    状态:dp(i,j)表示[1,i]范围内选取选取不相交的j个合法集合所得到的最大元素个数

    状态转移方程式:①在k个集合中:dp(i,j)=dp(pos,j-1)+i-pos   (pos是从i往前数第一个不在i所属集合中的元素位置)  ②不在k个集合中:dp(i,j)=dp(i-1,j)

    dp(i,j)=max{dp(pos,j-1)+i-pos,dp(i-1,j)}

    可以用递推式来实现,初始值:dp(i,0)=0,最终答案就是dp(n,k)

    AC代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn = 5000+10;
    int a[maxn],p[maxn];
    int dp[maxn][maxn];
    int main()
    {
        int n,k;
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        sort(a+1,a+n+1);
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++)
            pos[i]=lower_bound(a+1,a+n+1,a[i]-5)-a-1;
        for(int j=1;j<=k;j++)
            for(int i=1;i<=n;i++)
                dp[i][j]=max(dp[i-1][j],dp[pos[i]][j-1]+i-pos[i]);
        printf("%d
    ",dp[n][k]);
        return 0;
    }

    开始自己做这道题的时候想到动态规划,但设计得不够好导致时间复杂度更高。

    状态定义:dp(i,j,m)表示表示[i,j]范围内选取选取不相交的m个合法集合所得到的最大元素个数

    状态转移:dp(i,j,m)=dp(i,p,1)+dp(p,j,m-1)     (p∈[i,j])

    虽然经过优化,状态减少到2维,但是转移的时间复杂度O(n)优化不了,一直TLE 

    所以说,以后做dp题目的时候,再多思考一下子问题的设计;多刷,积累经验。

    F1. Spanning Tree with Maximum Degree

    题目大意:给你一个无向图,求任意一个生成树,要求其上度数最大的点的度数,尽可能大。

    题目思路:先计算所有点的度数,把度数最大的点以及其所连着的边加入正在构造的生成树,再添加边集中其它的边来构造生成树。

    AC代码:

    #include<bits/stdc++.h>
    using namespace std;
    #define maxn 200005
    
    int n,m; 
    int fa[maxn];
    int d[maxn];
    
    Init_DisjointSet()
    {
        for(int i=1;i<=m;i++)
            fa[i]=i; 
    } 
    int find(int k)    {return fa[k]==k?k:fa[k]=find(fa[k]);}
     
    struct Edge{
        int s,t;
    }edge[maxn];  
    
    bool vis[maxn]; 
     
    void work(int size)
    {
        int k1,k2;
        int cnt=0; 
        for(int j=1;j<=m;j++)
        {
            Edge tmp=edge[j];
            if(vis[j]) continue; 
            int k1=find(tmp.s);    
            int k2=find(tmp.t);
            if(k1!=k2) 
            {
                fa[k2]=k1;
                vis[j]=1;    
                cnt++;
                if(cnt==size) return;
            }
        }
    }
    
    int main()
    {
        int s,t;
        scanf("%d%d",&n,&m);
        memset(d,0,sizeof(d));
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&s,&t);
            edge[i].s=s;
            edge[i].t=t;
            d[s]++;
            d[t]++;
        }
        int p,maxd=0;
        for(int i=1;i<=n;i++)
        {
            if(maxd<d[i])
            {
                p=i;
                maxd=d[i];
             } 
        }
        memset(vis,0,sizeof(vis)); 
        Init_DisjointSet(); 
        for(int i=1;i<=m;i++)
        {
            s=edge[i].s;
            t=edge[i].t;
            if(s==p||t==p) 
            {
                vis[i]=true;
                int k1=find(s);    
                int k2=find(t);
                fa[k2]=k1;
            }
        }    
        work(n-1-maxd); 
        for(int i=1;i<=m;i++)  
            if(vis[i])
                printf("%d %d
    ",edge[i].s,edge[i].t);
        return 0;
    } 

    F2. Spanning Tree with One Fixed Degree

    题目大意:给你一个无向图,n个点m条边,问能否构造一个生成树,其1点的度数恰为D

    题目思路:

    两个情况下,无法构造该情形的生成树:1、1点所连着的边数小于D 2、去掉1点后,图中联通块的数目多于D

    其它情况下,均可以构造,步骤如下:

    1、将1点和每个联通块连一条边,共cnt条边

    2、剩下1点还需连出D-cnt条边,则任意从1点连出的边中选出其它D-cnt条边。

    3、从1点连出的点出发,遍历其它未经过的点,边遍历边添加所经过的边进入生成树的边集。

    经过以上三步即可构造完这颗生成树,输出所有点边。

    AC代码:

    #include<iostream>
    #include<vector>
    #include<cstring>
    using namespace std;
    #define pb push_back
    #define maxn 200005
    int vis[maxn];
    vector<int> g[maxn];
    vector<pair<int,int>> ans;
    bool mark[maxn];
    vector<int> block[maxn];
    void dfs(int s,int idex)
    {
        vis[s]=1;
        block[idex].pb(s);
        for(int i=0;i<g[s].size();i++)
        {
            int v=g[s][i]; 
            if(!vis[v]) dfs(v,idex);
        }
    }
    void dfs2(int s)
    {
        for(int i=0;i<g[s].size();i++)
        {
            int to=g[s][i];
            if(!vis[to])
            {
                ans.pb(make_pair(s,to));
                vis[to]=1;
                dfs2(to);
            }
        }
    }
    int main()
    {
        ios::sync_with_stdio(false);
        cin.tie(0);
        int n,m,D;
        int u,v;
        cin>>n>>m>>D;
        for(int i=1;i<=m;i++)
        {
            cin>>u>>v;
            g[u].pb(v);
            g[v].pb(u);
        }
        if(g[1].size()<D) return 0*puts("NO
    "); //1点起始度数至少应为D 
        memset(vis,0,sizeof(vis));
        vis[1]=1;
        int cnt=0; //去掉1点后剩下的联通块数目 
        for(int i=2;i<=n;i++)
        {
            if(!vis[i]) {
                cnt++;
                dfs(i,cnt); 
            }
        }
        if(D<cnt) return 0*puts("NO
    "); //联通块数目应不多于1点保留的出度 
        memset(vis,0,sizeof(vis));
        for(int i=0;i<g[1].size();i++)
        {
            int to=g[1][i];
            mark[to]=1;
        }
        int tmp=0;
        vis[1]=1;
        for(int i=1;i<=cnt;i++)//从1点向每个联通块连出一条边 
        {
            for(int j=0;j<block[i].size();j++)
            {
                int to=block[i][j];
                if(mark[to]) 
                {
                    ans.pb(make_pair(1,to));
                    vis[to]=1;
                    tmp++;
                    break;
                }
            }
        }
        if(tmp!=cnt) return 0*puts("NO
    ");
        for(int i=1;i<=n;i++)
        {
            if(mark[i]&&!vis[i]&&(D-cnt)) //从1点向没连到的点连边,直到度数为D 
            {
                ans.pb(make_pair(1,i));
                vis[i]=1;
                D--;
            }
        }
        for(int i=2;i<=n;i++)
            if(vis[i])    dfs2(i);//从深度为2的点开始深搜建树 
        cout<<"YES
    ";
        for(int i=0;i<ans.size();i++)
            cout<<ans[i].first<<' '<<ans[i].second<<endl;
        return 0;
     } 
  • 相关阅读:
    【转载】Java系列笔记(3)
    CentOS 7下Samba服务器的安装与配置
    Linux常用目录结构
    Linux计划任务crontab
    转:Linux 双网卡配置两个IP同时只有一个会通的原因
    centos7中搭建ntp服务器
    centos7中使用vg方式扩充root分区
    ping命令脚本实现显示网络状态、学生姓名、学号
    centos中基于随机数,再加入班级学生姓名
    centos7 shell脚本实现随机数
  • 原文地址:https://www.cnblogs.com/Andrew-aq/p/10499434.html
Copyright © 2011-2022 走看看