codeforces#430 题解

显然我还没有做E题,做了补上,C题和D题都是受了这位dalao的启发


这里写图片描述


A题很水了,注意一下long long就可以了,不开long long见祖宗。

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int main(){
    long long l,r,x,y,k;
    scanf("%lld%lld%lld%lld%lld",&l,&r,&x,&y,&k);
    for(register long long i=x;i<=y;i++){
        long long tmp=i*k;
        if(l<=tmp&&r>=tmp){printf("YES\n");return 0;}
    }
    printf("NO\n");
    return 0;
} 

B题也水,最开始因为英语不好读不懂题来着233,就是说现在有一个圆是原点为圆心,r为半径,然后还有一个圆是原点为圆心,r-d为半径,问完全位于大圆为全集,小圆关于大圆的补集范围内的圆有多少个…

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
double r,d,n,x,y,ri;
int cnt;
int main(){
    scanf("%lf%lf%lf",&r,&d,&n);
    for(register int i=1;i<=n;i++){
        scanf("%lf%lf%lf",&x,&y,&ri);
        double loc=sqrt(x*x+y*y);
        if(loc+ri<=r&&loc-ri>=r-d) cnt++;
    } 
    printf("%d\n",cnt);
    return 0;
}

C题还有点意思,就是给你一棵树,然后这棵树的每个点都有权值,然后我们要询问的是,每个点到根的遇到的所有权值的gcd最大是多少,然后对于每个点有这么几种情况,第一种是到根的所有数的gcd,第二种是删去任意一个到根的路径上的数(包括自己和根),然后剩下的所有数的gcd,然后再第一种和第二种的所有数中找出那个最大的数,就是这个点的答案。
显然这道题我们可以dfs地去处理,然后对于每一个点,我们从其father处转移,具体操作是对于每一个点我们都维护一个这个点的所有gcd的set,因为set是不可重集,所以显然到后面越来越多的数之后,gcd会变得越来越小,比如变成1之类的,所以不会超时,然后每次转移就可以了。
代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<set>
using namespace std;
const int MAXN=200000+10;
int n,a[MAXN],ff,tt,tail,head[MAXN];
set<int>s[MAXN];
int gcd(int m,int n){return (m==0)?n:gcd(n%m,m);} 
struct Line{
    int to,nxt;
}line[MAXN*2];
void add_line(int from,int to){
    line[++tail].to=to;
    line[tail].nxt=head[from];
    head[from]=tail;
}
void dfs(int u,int fa,int now){
    for(set<int>::iterator it=s[fa].begin();it!=s[fa].end();it++){
        int tmp=*it;
        s[u].insert(gcd(tmp,a[u]));
    }
    s[u].insert(now);
    s[u].insert(gcd(now,a[u]));
    for(register int i=head[u];i;i=line[i].nxt){
        int v=line[i].to;
        if(v==fa) continue;
        dfs(v,u,gcd(now,a[u]));
    }
}
int main(){
    scanf("%d",&n);
    for(register int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(register int i=1;i<=n-1;i++){
        scanf("%d%d",&ff,&tt);
        add_line(ff,tt);add_line(tt,ff);
    }
    dfs(1,0,0);
    for(int i=1;i<=n;i++)
        printf("%d%c",*(s[i].rbegin()),i==n?'\n':' ');
    return 0;
}

D题…完全想不出来,感觉只会暴力模拟,但是文章开头所说的那位大佬给了我很大的启发,原来还可以这么玩儿,首先这道题要求求的东西是,有一个集合A,然后对于正整数和0组成的集合为全集B,求A对于B的补集中最小的那个数,然后Q次询问,每次都把集合A里的所有数xor一个数,然后求新的A对于B的补集中最下的那个数。
我们可以这样思考:
①:因为xor具有累积性,所以对于之后的xor,实际上是一个xor的前缀异或和去异或初始A数组。
②:我们可以思考如下定理,如果最开始的A集合关于B集合的补集是C集合,那么C集合的所有数异或那个数之后得到的集合的最小值就是我们要求的值。
证明:
引理:对于一个数a,如果我们知道了他异或某个数b的结果是c,那么我们可以唯一确定这个数

1001010011101

XOR(x)
=1010111000100

显然我们可以唯一确定出x=0011101011001
好的,那么如果是原来的A集合关于B集合的补集C集合里的数,他异或上一个数,可能是原来的A集合里的一个数异或上同一个数得到的结果相同吗?显然是不可能的,所以C集合里的所有数异或上这个数之后得到的数其实也就是这个补集,所以我们把所有的不在A集合里的数先建成一个trie树,然后再去跑xor,找到一个最小异或值,做法是在trie数上分解我们要异或的值x,如果这一位等于1的话,那么如果可以在trie树上找到1就往1这边周,如果等于零的话,可以找到0就往0这边走,如果分别找不到,就往另一边走即可,最后走道叶节点获取到值再返回出来异或就行了。显然我们建树的时候可以固定一个深度,比如19或者20等等,这样在查询的时候会方便许多。
代码:

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;  
const int N=19;  
struct Tree{int ch[2],val;}tree[10000000];
int n,m,a[N+10],tail=1;  
bool judge[1<<N];  
void insert(int x){
    int loc=1;
    for(register int i=N-1;i>=0;i--){
        int tmp=(x>>i)&1;
        if(tree[loc].ch[tmp]==0){
            tree[loc].ch[tmp]=++tail;
        }
        loc=tree[loc].ch[tmp];
    }
    tree[loc].val=x;
}
int query(int x){
    int loc=1;
    for(register int i=(N-1);i>=0;i--){
        int tmp=(x>>i)&1;
        if(tree[loc].ch[tmp]==0) loc=tree[loc].ch[tmp^1];
        else                     loc=tree[loc].ch[tmp];
    }
    return tree[loc].val;
}
int main(){
    scanf("%d%d",&n,&m);int tmp;
    for(register int i=1;i<=n;i++){scanf("%d",&tmp);judge[tmp]=true;}
    for(register int i=0;i<=(1<<N)-1;i++)
        if(judge[i]==false)insert(i);
    int now=0;
    for(register int i=1;i<=m;i++){
        scanf("%d",&tmp);now^=tmp;
        printf("%d ",now^query(now));
    }
    return 0;
}
/*
2 2
1 3
1
3
*/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值