zoukankan      html  css  js  c++  java
  • 【题解】:中南大学第十二届大学生程序设计竞赛

    ps:感觉比昨天晚上的华师题目质量高一点,至少emmm,题面没有那么多bug。

    下面忽略了水题,只给出我觉得有价值的几个题目的题解和代码:

    【C:神山神石】:

    题意:

               每一天,Wells可以喂神山一个神石,使得神山的高度变为原高度乘上神石的能力值,或者使得所有的神石能力值+1。第 0 天时,所有神石的能力值为 0,神山的高度为 1。Wells想知道神山最快的长成刚好某一个高度值最少需要多少天。输入L,R,P(L,R<1e9; P<=100);求多少个x满足L<=x<=R,最小天数小于P。

    思路:

               注意是刚好长到x的最小天数,不能超过x。通过暴力搜索,我们发现能够在100天内长到的高度有4e6个左右,把这4e6个离散,离散后是3e6左右个,所以我们应该想办法找到可以长到的高度,然后判断是否在[L,R]区间里,具体的:结果等于加法次数+乘法次数,加法次数等于最大发因子,乘法次数可以用背包求解最优。(充分利用单调性)

     (代码做了注释,理解起来不难。)

    #include<map>
    #include<cmath>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define ll long long
    using namespace std;
    const int P[29]={0,2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,57,59,61,67,71,73,79,83,89,91,97,101};
    const int INF=1000000000;
    const int maxn=5000000;
    int num[maxn],Dis[maxn],ans,L,R,p;
    bool FF[maxn];
    void dfs(int now,int x)
    {
        if (now>28){
            num[++num[0]]=x;
            return;
        }
        while(true){
            dfs(now+1,x);
            if(R/x<P[now]) break;
            x*=P[now];
        }
    }
    int main()
    {    
        cin>>L>>R>>p; dfs(1,1);
        for (int i=1;i<=num[0];i++) Dis[i]=INF;
        Dis[1]=0; //cout<<num[0]<<endl;
        sort(num+1,num+num[0]+1);
        num[0]=unique(num+1,num+num[0]+1)-(num+1);
        //cout<<num[0]<<endl;
        for(int i=2;i<=p;i++){  //枚举加法的天数,即增加神石的能量。 
            int l=1; 
            for(int j=1;j<=num[0];j++){
                while(l<=num[0]&&num[l]<i*num[j]) l++; 
                //得到num[l]==num[j]*i。因为二者dfs都存在,所以第一个大于等于的其实就是等于 (如果不超过R) 
                if(l<=num[0]){
                    Dis[l]=min(Dis[j]+1,Dis[l]); //Dis是做乘法的天数 
                    if(Dis[l]+i<=p) FF[l]=true;
                }
            }
        }
        for(int i=1;i<=num[0];i++)
            if (FF[i]&&num[i]<=R&&num[i]>=L) ans++;
        printf("%d
    ",ans); 
        return 0;
    }
    View Code

    【D:I'm new here】

    题意:

                给定一棵树(N<1e5),树边上有边权,现在求任意点对的路径异或值的和。

    思路:

                因为两点之间的异或Xor(u,v)=Xor(root,u)^Xor(root,v)。所以我们DFS得到每个点到根的异或值Xi。然后计算两两异或值。因为异或没有结合性,所以不能用前缀和来计算。正确的打开方式是对每一位sig,我们计算1的个数(cnt)和0的个数(N-cnt),那么这一位的贡献就是cnt*(N-cnt)*(1<<sig)。然后累计即可。(但是比赛的时候(long long位置没有写好),打死也没有找到bug。。。真让人脑壳大)  

    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define ll long long
    using namespace std;
    const int maxn=100010;
    int Laxt[maxn],Next[maxn<<1],To[maxn<<1],val[maxn<<1],cnt;
    int Xor[maxn],ct[maxn]; 
    ll ans;
    void add(int u,int v,int w){
        Next[++cnt]=Laxt[u];
        Laxt[u]=cnt;
        To[cnt]=v;
        val[cnt]=w;
    }
    void init()
    {
        cnt=0; ans=0;
        memset(Laxt,0,sizeof(Laxt));
        memset(ct,0,sizeof(ct));
    }
    void dfs(int u,int fa)
    {
        for(int i=Laxt[u];i;i=Next[i]){
            if(To[i]!=fa){
                Xor[To[i]]=Xor[u]^val[i];
                dfs(To[i],u);
            }
        }
    }
    int main()
    {
        int T,N,i,j;
        scanf("%d",&T);
        while(T--){
            init();
            scanf("%d",&N);
            for(i=1;i<N;i++){
                int u,v,w;
                scanf("%d%d%d",&u,&v,&w);
                add(u,v,w); add(v,u,w);
            }
            dfs(1,0);
            for(i=1;i<=N;i++)
             for(j=0;j<32;j++)
              if(Xor[i]&(1<<j))
               ct[j]++;
            for(j=0;j<32;j++)
               ans=ans+((ll)ct[j]*(N-ct[j]))*(1LL<<j);
            cout<<ans<<endl;
        }
        return 0;
    }
    View Code

    【E:EZ's binoculars】

    题意:

                 给你一些点(N<1e5),然后其实是每次询问给你一个中心在( x , y )的四边相等的菱形,对角线长为d,求问多少点在菱形里。

    官方题解:

                 首先可以想到二维树状数组维护。但是显然内存不允许,时间复杂度也不允许。 所以需要离线优化 那么考虑优化,可以基于离线排序后优化。现将所有的点P按照( x + y )关键字排序,然后将菱形的四个点按照( x + y )排序。 那么,每次如果P的(x+y)<= 菱形的(x+y),那么将p的(y-x)离散化后的值插入到树状数组 那么查询菱形中一点 t 下方有多少个点时,就是查询小于等于t.y - t.x的点的个数,树状数组查询即可(必须离散化后查询)。 所以时间复杂度就是O(nlgn)。

                 (实现也不那么容易)

     (占位)

    【F:Lunch War with the Donkey】

    题意:

             现在有N瓶牛奶和M个面包,现在要求搭配成min(N,M)队,每一队有一瓶牛奶和一个面包,价值是a牛奶*b面包,求最大和最小。

    思路:

             排序,最大值的话,显然是从大到小前面min(N,M)个分别相乘;最小的话,显然是后面min(N,M)个,但是注意要交错相乘(排序不等式)。

    #include<cmath>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define ll long long
    using namespace std;
    const int maxn=100010;
    ll a[maxn],b[maxn],ans1,ans2;
    int main()
    {
        int T,N,M,i; 
        while(~scanf("%d%d",&N,&M)) {
            ans1=ans2=0;
            for(i=1;i<=N;i++) scanf("%d",&a[i]);
            for(i=1;i<=M;i++) scanf("%d",&b[i]);
            sort(a+1,a+N+1); sort(b+1,b+M+1);
            for(i=1;i<=min(N,M);i++) ans1+=a[N+1-i]*b[M+1-i];
            N=min(N,M);
            for(i=1;i<=N;i++) ans2+=a[i]*b[N+1-i];
            cout<<ans1<<" "<<ans2<<endl;
        }
        return 0;
    }
    
    /**********************************************************************
        Problem: 2084
        User: nimphy
        Language: C++
        Result: AC
        Time:1124 ms
        Memory:3584 kb
    **********************************************************************/
    View Code

    【J:Pigs can't take a sudden turn】

    题意:

           两个点以两个恒定的速度移动,问过程中两点的最短距离。

    思路:

           可以三分;可以用二次函数不等式(特判相遇的情况); 也可以:

          用相对运算,假设一个点不动,则另一个点的相对运动轨迹就是一条线段,然后求点到线段最短距离。

    #include<cmath>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define ll long long
    using namespace std;
    int x1,yy1,x2,y2,u1,v1,u2,v2;
    double ans;
    int main()
    {
        int T,Case=0;
        cin>>T;
        while(T--){
            cin>>x1>>yy1>>x2>>y2>>u1>>v1>>u2>>v2;
            u1-=u2; v1-=v2;
            if(u1==0&&v1==0) { //相对速度为0 
                ans=(x1-x2)*(x1-x2)+(yy1-y2)*(yy1-y2); 
                ans=sqrt(ans);
            } 
            else if(x1==x2&&yy1==y2) ans=0.0; //开始就重合 
            else {
                ans=(x1-x2)*(x1-x2)+(yy1-y2)*(yy1-y2); 
                ans=sqrt(ans);
                if((x2-x1)*u1+(y2-yy1)*v1>=0) //如果小于等于90度:点到直线的距离 
                  ans=abs(-v1*x2+u1*y2+v1*x1-yy1*u1)/sqrt(u1*u1+v1*v1);
            }
            printf("Case %d: %.6lf
    ",++Case,ans);
        }
        return 0;
    }
    View Code
  • 相关阅读:
    修改host指定域名指向ip,Windows脚本与Linux脚本
    Linux磁盘分区/格式化/挂载目录
    给普通用户赋予sudo权限后报错,提示/etc/sudoers文件权限拒绝
    SUSE12-SP2安装教程(虚拟机)
    ios键盘弹起 body的高度拉长,页面底部空白问题。ios软键盘将页面抵到上面后,关闭软键盘页面不回弹的问题。
    Visual Studio动态生成版权信息
    微信发支付宝红包(花呗)
    纯CSS打造淘宝导航菜单栏
    用bat批处理程序通过DOS命令行删除所有的空文件夹
    从0开始用U盘制作启动盘装Windows10系统(联想R720笔记本)并永久激活方法
  • 原文地址:https://www.cnblogs.com/hua-dong/p/8908690.html
Copyright © 2011-2022 走看看