Codeforces Round #788 (Div. 2)

Codeforces Round #788 (Div. 2)

E.Hemose on the Tree

题意:给定一颗节点个数为\(n=2^p\)个的树,需要给每条边和每个点赋值(1,2*n-1),每个数不重复,要求使得一条路径上的最大异或和最小的方案
做法:其实这个有点脑经急转弯的意思,因为发现其实不存在一种方案可以使得最大异或和小于n,那么是否能构成全部等于n的方案呢,这是可以的,并且很简单,那么题目就变成了需要构造最大疑惑和等于n的方案,根节点设为n,1和n+1,2和n+2,异或起来都是n,所以我们只需要把这些值分别赋值到边和点上就可以了,需要注意一下,路径上异或和为0时,n+i在边上,为n时,n+i在点上,这样保证所有异或和都小于等于n

点击查看代码
#include <bits/stdc++.h>
using namespace std;

const int maxn=3e5+10;
int val[maxn],Next[maxn],Head[maxn],ver[maxn];
int edge[maxn];
int tot=-1,n,num;

void Insert(int u,int v){
    ver[++tot]=v;
    Next[tot]=Head[u];
    Head[u]=tot;
}

void dfs(int x,int f,int e,int flag){
    if(!val[x]) {
        num++;
        if(flag)val[x]=num+n,edge[e]=edge[e^1]=num;
        else val[x]=num,edge[e]=edge[e^1]=num+n;
    }
    for (int i=Head[x];i!=-1;i=Next[i]){
        if(ver[i]!=f){
            dfs(ver[i],x,i,flag^1);
        }
    }
}

void init(){
    tot=-1,num=0;
    for (int i=0;i<=2*n;i++) val[i]=0,Next[i]=Head[i]=ver[i]=-1;
}

int main(){
    #ifdef lmj_debug
        freopen("1.in","r",stdin);
    #endif
    int T;
    cin>>T;
    while(T--){
        int p;
        scanf("%d",&p);
        n=1<<p;
        init();
        for (int i=1;i<n;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            Insert(u,v);
            Insert(v,u);
        }
        val[1]=n;
        dfs(1,1,1,1);
        printf("1\n");
        for (int i=1;i<=n;i++) printf("%d ",val[i]);
        printf("\n");
        for (int i=0;i<=tot;i=i+2) printf("%d ",edge[i]);
        printf("\n");
    }
    return 0;
}

F.Jee, You See?

题意:题意:求满足以下条件的 \(n\) 维向量 \(a=(a_1,a_2,a_3...a_n)\) 的个数:

  • \(l<=a_1+a_2+a_3...a_n<=r\)
  • \(a_1 xor a_2xora_3...a_n=X\)
    做法:可以转化问题,若能求出满足以下条件的数量:
  • \(1<=a_1+a_2+a_3...a_n<=r\)
  • \(a_1 xor a_2xora_3...a_n=X\)

\(r\) 对应的答案减去 \(l-1\) 对应的答案即可。
那么怎末考虑这个问题呢?我们按位来考虑,首先考虑暴力做法,总共最多就64位,枚举每一位可以选1,2,3...n个,但是这样的做法复杂度\(O(n^{64})\),考虑一下,怎么通过前几位的值来推现在的方案,而且又因为和还不能超过r,所以还需要知道当前位最多能枚举多少
考虑设\(dp[i][j]\),表示,考虑高位的前\(i\)位的时候,剩下\(j\)位可以用,那么当前位能用的最多是多少位呢?最多到\(2*j\)位,因为二进制中\(2^k\)\(2*2^{k-1}\)的关系,那么前一位剩j位给我搞,我这一位不就最多能搞\(2*j\)位,但是如果r的当前位为1,还可以再加一位,所以设当前可以使用的位数为left所以转移方程\(dp[i][min(left-k,n)]=\sum_{k=0}^{k<=min(n,left)}dp[i+1][j]*C(n,k)\)

点击查看代码
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int maxn=1e3+10;

const ll mod=1e9+7;

ll dp[64][maxn],C[maxn][maxn];

ll solved(ll r,ll z,int n){
    memset(dp,0,sizeof(dp));
    dp[63][0]=1;
    for (int i=62;i>=0;i--){
        int add=(r>>i&1ll);
        int par=(z>>i&1ll);
        for (int j=0;j<=n;j++){
            int left=j*2+add;
            auto Minn = [=]{return n<left?n:left;};
            for (int k=par;k<=Minn();k+=2){
                int nowl=min(left-k,n);
                dp[i][nowl]=(dp[i][nowl]+dp[i+1][j]*C[n][k]%mod)%mod;
            }
        }
    }
    ll ans=0;
    for (int i=0;i<=n;i++){
        ans+=dp[0][i];
        ans%=mod;
    }
    //cout<<ans<<endl;
    return ans%mod;
}

int main(){
    #ifdef lmj_debug
        freopen("1.in","r",stdin);
    #endif
    C[0][0]=1;
    for (int i=1;i<=1000;i++){
        for (int j=0;j<=i;j++){
            if(j==0) C[i][j]=1;
            else C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
        }
    }

    ll n,l,r,z;
    cin>>n>>l>>r>>z;
    printf("%lld\n",((solved(r,z,n)-solved(l-1,z,n))%mod+mod)%mod);
    return 0;
}
复杂度$O(62*n^2)$
posted @ 2022-05-09 22:27  lmj_1  阅读(72)  评论(0)    收藏  举报