2018.7.17 绍兴一中模拟赛 解题报告

成绩:100+100+50=250(考得不错,只可惜跑得太慢,是250中最慢的一个)

T1:交换(swap)

满分解法1:

这题是真的水。
先介绍一个比较麻烦的做法,也是我考试时的做法。
大致思路就是将每个数与其应该在的位置放在一个集合中(用并查集可以轻松实现),然后判断有多少个集合,最终答案就是n-集合数。
代码如下:

#include<bits/stdc++.h>
#define N 1000000
using namespace std;
int n,a[N+5],b[N+5],fa[N+5],s[N+5];
inline char tc()
{
    static char ff[100000],*A=ff,*B=ff;
    return A==B&&(B=(A=ff)+fread(ff,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(int &x)
{
    x=0;char ch;
    while(!isdigit(ch=tc()));
    while(x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc()));
}
inline void write(int x)
{
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
inline int getfa(int x)
{
    return fa[x]==x?x:fa[x]=getfa(fa[x]);
}
int main()
{
    freopen("swap.in","r",stdin),freopen("swap.out","w",stdout);
    register int i;
    for(read(n),i=1;i<=n;++i) read(a[i]),fa[i]=i;
    int ans=0;
    for(i=1;i<=n;++i) fa[getfa(i)]=getfa(a[i]);//将每个数与其应在的位置放入一个集合内
    for(i=1;i<=n;++i) if(!s[getfa(i)]) s[getfa(i)]=1;else ++ans;//如果不是第一次访问该集合,则将ans加1,最终的ans等价于n-集合数
    return write(ans),0;
}
满分解法2:

考后,我才知道还有一个更简便的方法。
对于每个位置不对的数,直接将其与它应在的位置上的数交换一下,然后将ans加1即可,比我的方法简单多了。
代码如下:

#include<bits/stdc++.h>
#define N 1000000
#define swap(x,y) (x^=y,y^=x,x^=y)
using namespace std;
int n,a[N+5];
inline char tc(void)
{
    static char ff[100000],*A=ff,*B=ff;
    return A==B&&(B=(A=ff)+fread(ff,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(int &x)
{
    x=0;char ch;
    while(!isdigit(ch=tc()));
    while(x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc()));
}
inline void write(int x);
{
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
int main()
{
    freopen("swap.in","r",stdin),freopen("swap.out","w",stdout);
    register int i;
    for(read(n),i=1;i<=n;++i) read(a[i]);
    int ans=0;
    for(i=1;i<=n;++i) if(a[i]^i) swap(a[i],a[a[i]]),++ans;//判断一个数是否在其应在的位置
    return write(ans),0;
}

T2:兔子抓狼(rabbit)

满分解法:

这题的满分解法其实挺好做的。
其实,我们完全可以将题意转换一下,改成这样:让n只兔子全部直线跑向狼的巢。这样就非常可做了。
我们可以把n只兔子到达狼巢的距离sort一下,然后依次比较每只兔子与狼谁先到达狼巢,若有某只兔子比狼迟到狼巢,则答案就是当前兔子的编号减1(我们已将兔子到狼巢的距离sort过了,既然当前这只兔子无法及时到达,它后面的兔子就更没法及时到达),若每只兔子都比狼早到狼巢,则在最后输出n。
由于本题中的距离是欧几里得距离,所以要注意精度问题,我建议这题在操作过程中不要开方,可以直接存储距离的平方,毕竟不会爆long long
那么,这题就非常水了。
代码如下:

#include<bits/stdc++.h>
#define N 500000
#define LL long long
using namespace std;
LL n,t;
struct w{
    LL x,y,d;
}e[N+5];
inline char tc()
{
    static char ff[100000],*A=ff,*B=ff;
    return A==B&&(B=(A=ff)+fread(ff,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(LL &x)
{
    x=0;int f=1;char ch;
    while(!isdigit(ch=tc())) if(ch=='-') f=-1;
    while(x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc()));
    x*=f;
}
inline void write(LL x)
{
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
LL dis(w x)//求出一个点到狼巢的欧几里得距离,由于精度问题,返回的是距离的平方
{
    return 1LL*(x.x-1e7)*(x.x-1e7)+1LL*x.y*x.y;
}
bool cmp(w x,w y)
{
    return x.d<y.d;
}
int main()
{
    freopen("rabbit.in","r",stdin),freopen("rabbit.out","w",stdout);
    register int i;
    for(read(n),read(t),i=1;i<=n;i++) read(e[i].x),read(e[i].y),e[i].d=dis(e[i]);
    sort(e+1,e+n+1,cmp);//按照到狼巢的距离排序
    LL x=1e7;//狼到狼巢的距离
    for(int i=1;i<=n;i++)
    {
        if(1LL*x*x>=dis(e[i])) x+=t;//比较距离的平方,并将狼到狼巢的距离加上被推迟的时间,是一个简单的转换
        else return write(i-1),0;//如果不能及时到达,则直接输出答案并退出主函数
    }
    return write(n),0;
}

T3:魔法(magic)

50分做法:

最简单的暴力, O ( n 2 ) 枚举两个点求异或值,并将其位数相加。

满分做法:

我们可以将每个数在二进制下分解,并将其插入一棵01字典树
求答案时,只要在01字典树上从根节点开始向子树遍历(具体遍历过程见代码),即可快速求出答案。
代码如下:

#include<bits/stdc++.h>
#define LL long long
#define N 50000
using namespace std;
LL n,ans,tot=1,a[N+5];
struct Trie
{
    LL Num,Size,Son[2];
}s[N*60+5];
inline char tc()
{
    static char ff[100000],*A=ff,*B=ff;
    return A==B&&(B=(A=ff)+fread(ff,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(LL &x)
{
    x=0;char ch;
    while(!isdigit(ch=tc()));
    while(x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc()));
}
inline void write(LL x)
{
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
inline void Insert(LL num,LL &x,LL k)//字典树的插入操作
{
    if(!x) s[x=++tot].Size=0;//若当前节点是空节点,则动态开点
    ++s[x].Size;//将该子树的大小加1
    if(!(~k)) return;//若当前k值为-1,则退出函数
    if(num&(1LL<<k)) Insert(num,s[x].Son[1],k-1);//判断原数右起第(k-1)位是否为1,若为1,则向当前节点的右儿子继续插入该数
    else Insert(num,s[x].Son[0],k-1);//若为0,则向当前节点的左儿子继续插入该数
}
inline void Query(LL x,LL y,LL num,LL k)//询问操作
{
    if(!x||!y) return;//如果x和y两个节点中有一个是空节点,就退出函数
    if((1LL<<(k+1))<=num) return;//如果无论如何取值都不可能大于num,则直接退出
    if((1LL<<k)>=num) ans+=1LL*s[s[x].Son[0]].Size*s[s[y].Son[1]].Size,Query(s[x].Son[0],s[y].Son[0],num,k-1),Query(s[x].Son[1],s[y].Son[1],num,k-1);//如果该位取1大于等于num,则更新ans(让ans加上一个x和y中一个节点的左子树大小和一个节点的右子树大小的积),并尝试将该位取值为0(让x和y同时变为它的左子树或右子树),继续寻味
    else Query(s[x].Son[0],s[y].Son[1],num-(1LL<<k),k-1),Query(s[x].Son[1],s[y].Son[0],num-(1LL<<k),k-1);
}//如果该位取1小于num,则该位必须为1(让x和y变成不同的子树)
int main()
{
    freopen("magic.in","r",stdin),freopen("magic.out","w",stdout);
    register int i;LL x,t;
    for(read(n),i=1;i<=n;++i) read(a[i]),Insert(a[i],x=1,59);//边读入边插入读进的数
    for(i=t=1;i<=19;++i,t=(t<<3)+(t<<1)) Query(1,1,t,59);//对每一种位数进行询问,可以发现,一个k位的数会被重复k次查询到,因此刚好将其贡献乘了k,不用额外去处理了
    return write(ans),0;
}

猜你喜欢

转载自blog.csdn.net/chenxiaoran666/article/details/81139693
今日推荐