Live2d Test Env

【题解】:中南大学第十二届大学生程序设计竞赛

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\n",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\n",++Case,ans);
    }
    return 0;
}
View Code

 

posted @ 2018-04-22 18:31  nimphy  阅读(381)  评论(0编辑  收藏  举报