zoukankan      html  css  js  c++  java
  • 2020CCPC长春站题解A D F H J K

      都过了一周了,才来补题,不愧是我,摸鱼的神。

    A - Krypton

      题意:充n块钱,不同档位有首冲奖励,问最多能得游戏币。

      思路:充n块钱,能得n*10的游戏币(一开始没注意,+1告诉我才发现),然后首冲奖励每个档位只有有一次就是01背包了。

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 typedef long long ll;
     4 const int N=2e3+11;
     5 const int a[11]={
     6     1,6,28,88,198,328,648,
     7 };
     8 const int b[11]={
     9     8,18,28,58,128,198,388,
    10 };
    11 int dp[N];
    12 int main(){
    13     int n;
    14     scanf("%d",&n);
    15     for(int i=0;i<7;++i){
    16         for(int j=n;j>=a[i];--j) dp[j]=max(dp[j],dp[j-a[i]]+b[i]);
    17     }
    18     printf("%d
    ",10*n+dp[n]);
    19     return 0;
    20 }
    冲冲冲

    D - Meaningless Sequence

      题意:

       思路:一开始看出an&i打表打错了,然后按照an&i进行打表,就可以看出比如c是3时

      n:  0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

      an:1 3 3 9 3 9 9 27 3 9 9 27 9 27 27 81 3

      可以观察到,从2^i开始到2^(i+1)-1的数是0到2^i-1的数的c倍(又是+1告诉我才发现的,我真是粗心鬼)

      当时赛场上是让+1敲了,我自己做一遍的话思路与赛场上一样,依旧是一个类似快速幂的思想。

      我们可以先预处理出到每一位i时的前n项和,从高位到低位,有1的位就统计答案,同时将下一位的权重增加c倍。

      因为是从0开始,然后最高位有个3,那么我们可以先把这个3拿出来,把后面的位的答案加上先,最后再把这个3加回去(此时权重变化,可能就不再是3了)。

      比如n=1101,c=3,那么这时就是求1 3 3 9 3 9 9 27 3 9 9 27 9 27这一段的和

      单独看每个1的话,第一个1自然是1 3 3 9 3 9 9 27 3这一段,第二个1是1 3 3 9,第三个1是1 3这一段,

      然后我们把第一个1的3先去掉,也就是变成了1 3 3 9 3 9 9 27后面接上3*(1 3 3 9)再接上9*(1 3),最后把27加上。

    //可以打表观察到,从2^i开始到2^(i+1)-1的数是0到2^i-1的数的c倍
    //那么我们可以先预处理出到每一位i时的前n项和,然后有1的地方便统计答案
    //每统计一次,这个i长度的前缀和就乘c 
    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N=3e3+11,md=1e9+7;
    char s[N];
    ll a[N];
    int main(){
        int n,c;
        a[0]=1;
        printf("1 ");
        for(int i=1;i<=16;++i){
            ll mx=0;
            for(int j=0;j<i;++j) mx=max(mx,a[i&j]);
            a[i]=mx*3;
            printf("%lld ",a[i]); 
        }
        scanf("%s%d",s,&c);
        n=strlen(s); 
        for(int i=1;i<n;++i) a[i]=a[i-1]*(c+1)%md;
        ll pw=1,ans=0; 
        for(int i=0;i<n;++i){
            if(s[i]=='1'){
                ans=(ans+pw*a[n-i-1]%md)%md;
                pw=pw*c%md;
            }
        }
        ans=(ans+pw)%md;
        printf("%lld
    ",ans);
        return 0;
    }
    快快快

     F - Strange Memory

      题意:树上的点有权值ai,求

      思路:树启发模板题,第二次做树启题,有点不自信,跟学弟口胡半天没敢写,最后按照一开始的思路,直接过了,意料之外的没有超时。

      对于异或结果相同的位置对,我们只需要去拆位,然后统计下每个数相应的每一位有多少个0和1即可。需要注意异或结果超1e6,用来保存结果的桶得开大点。

      核心思路便在于,对于当前点u,假设我们已经统计下了某个儿子v1上的权值情况,那么我们可以再去枚举v2上的点,就可以在v1中去找相应的a[u]^a[v2]计算对答案的贡献。

      那么便根据重链和轻链的思想,对于当前节点,我们已经保存了重儿子的信息,那么接下来只需要再去枚举轻儿子即可。但这样的话只算了轻对重的贡献,还有轻对轻的贡献,也要考虑。我的处理是,当前节点跑完某个轻儿子后,再跑一遍这个轻儿子,把它的信息保存下来,算完当前节点子树对答案的贡献之后,再又把它轻儿子的信息去掉。

    //树上启发式,多跑一遍算答案 
    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N=1e5+11,M=2e6+11;
    vector<int> vv[N];
    ll ans=0;
    int a[N],size[N],son[N],cnt[M][21][2];
    void dfs1(int u,int f){
        size[u]=1;
        int lenv=vv[u].size();
        for(int i=0;i<lenv;++i){
            int v=vv[u][i];
            if(v==f) continue;
            dfs1(v,u);
            size[u]+=size[v];
            if(size[v]>size[son[u]]) son[u]=v;
        }
        return ;
    }
    void add(int x,int val){
        for(int i=0;i<20;++i){
            if(x&(1<<i)) cnt[a[x]][i][1]+=val;
            else cnt[a[x]][i][0]+=val;
        }
        return ;
    }
    void ac(int x,int y){
        for(int i=0;i<20;++i){
            if(y&(1<<i)) ans+=(1ll<<i)*cnt[x][i][0];
            else ans+=(1ll<<i)*cnt[x][i][1];
        }
        return ;
    }
    void dot2(int u,int f,int tf,int val){
        add(u,val);
        int lenv=vv[u].size();
        for(int i=0;i<lenv;++i){
            int v=vv[u][i];
            if(v==f||v==son[tf]) continue;
            dot2(v,u,tf,val);
        }
    }
    void dot1(int u,int f,int tf){
        ac(a[u]^a[tf],u);
        int lenv=vv[u].size();
        for(int i=0;i<lenv;++i){
            int v=vv[u][i];
            if(v==f||v==son[tf]) continue;
            dot1(v,u,tf);//统计答案 
            if(u==tf) dot2(v,u,tf,1);//加上轻儿子信息 
        }
        if(u==tf){
            for(int i=0;i<lenv;++i){
                int v=vv[u][i];
                if(v==f||v==son[tf]) continue;
                dot2(v,u,tf,-1);//把轻儿子信息去掉 
            }
        }
    }
    void dfs2(int u,int f,int op){
        int lenv=vv[u].size();
        for(int i=0;i<lenv;++i){
            int v=vv[u][i];
            if(v==f||v==son[u]) continue;
            dfs2(v,u,0);
        }
        if(son[u]) dfs2(son[u],u,1);
        //多加个函数计算答案 
        dot1(u,f,u);
        //正常树上启发式过程 
        dot2(u,f,u,1);
        if(!op) dot2(u,f,0,-1);
    }
    int main(){
        int n,u,v;
        scanf("%d",&n);
        for(int i=1;i<=n;++i) scanf("%d",&a[i]);
        for(int i=1;i<n;++i){
            scanf("%d%d",&u,&v);
            vv[u].push_back(v);
            vv[v].push_back(u);
        }
        ans=0;
        dfs1(1,0);
        dfs2(1,0,1);
        printf("%lld
    ",ans);
        return 0;
    }
    启启启

     H - Combination Lock

      题意:一个锁,有n位数,每位数取值0~9,每次操作可以把某一位+1或者-1%10,有些状态不能到达。不能操作就输,问先手能不能赢。

      思路:当前+1和学弟这题初步的思路挂了,又没有什么队出这题的时候,我还以为这题很难,就没有深入的想。但这题知道思想后,真不难,我真是个小垃圾,那时没有继续的跟队友进行深入的讨论。

      之前做过个一般图博弈,而这个二分图博弈也类似,胜败就是看还有没有增广路。

      每个状态的数位和要么是奇数要么是偶数,而每次操作便是改变了奇偶。那么状态可以分成X集合和Y集合,X集合一次操作能到达Y集合某个状态,便是它们之间存在边。

      那么先手能不能赢,便看加入初状态后,存不存在一条新的增广路。用最大流来写的话,就是看加入初状态,能不能使得最大流量增加。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N=1e5+11,M=N*71,inf=0x3f3f3f3f;
    struct Side{
        int v,ne,w;
    }S[M];
    int ban[N],iso[N];
    int n,sn,sb,se,head[N],dep[N],cur[N];
    void inito(){
        for(int i=0;i<N;++i){
            int temp=i,sum=0;
            while(temp){
                sum+=temp%10;
                temp/=10;
            }
            iso[i]=sum&1;
        }
    }
    void init(int n){
        sn=0;
        sb=n-1;se=n;
        for(int i=0;i<=n;i++){
            ban[i]=0;
            head[i]=-1;
        }
    }
    void addE(int u,int v,int w){
        S[sn].w=w;S[sn].v=v;
        S[sn].ne=head[u];
        head[u]=sn++;
    }
    void addS(int u,int v,int w){
        addE(u,v,w);addE(v,u,0);
    }
    bool bfs(int n){
        queue<int> q;
        for(int i=0;i<=n;i++) dep[i]=0;
        dep[sb]=1;
        q.push(sb);
        while(!q.empty()){
            int u=q.front();
            q.pop();
            for(int i=head[u];~i;i=S[i].ne){
                int v=S[i].v;
                cur[v]=head[v];
                if(!dep[v]&&S[i].w>0){
                    dep[v]=dep[u]+1;
                    q.push(v);
                    if(v==se) return 1; 
                }
            }
        }
        return 0;
    }
    int dfs(int u,int minf){
        if(u==se||!minf) return minf;
        for(int &i=cur[u];~i;i=S[i].ne){
            int v=S[i].v;
            if(S[i].w>0&&dep[v]==dep[u]+1){
                int flow=dfs(v,min(minf,S[i].w));
                if(flow>0){
                    S[i].w-=flow;
                    S[i^1].w+=flow;
                    return flow;
                }
            }
        }
        return 0;
    }
    int dinic(int n){
        int ans=0,flow=0;
        while(bfs(n)){
            while(flow=dfs(sb,inf)) ans+=flow;
        }
        return ans;
    }
    int main(){
        inito();
        int t;
        scanf("%d",&t);
        while(t--){
            int n,m,beg,x,lim=1;
            scanf("%d%d%d",&n,&m,&beg);
            for(int i=1;i<=n;++i) lim*=10;
            init(lim+2);
            while(m--){
                scanf("%d",&x);
                ban[x]=1;
            }
            for(int i=0;i<lim;++i){
                if(ban[i]) continue;
                if(iso[i]){
                    if(i!=beg) addS(sb,i,1);
                    for(int j=1;j<lim;j*=10){
                        int pw=i/j%10,v1,v2;
                        if(pw==9) v1=i-9*j;
                        else v1=i+j;
                        if(pw==0) v2=i+9*j;
                        else v2=i-j;
                        if(!ban[v1]) addS(i,v1,1);
                        if(!ban[v2]) addS(i,v2,1);
                    }
                }else if(i!=beg) addS(i,se,1);
            }
            dinic(lim+2);
            if(iso[beg]) addS(sb,beg,1);
            else addS(beg,se,1);
            if(dinic(lim+2)) printf("Alice
    ");
            else printf("Bob
    ");
        }
        return 0;
    }
    博博博

    J - Abstract Painting

      题意:已经存在一些有限制的圆,再往上加同样有限制的圆,问有多少种方案。

      思路:思路来源

      因为半径只有5,那么每个点能不能在某个圆上只与它周围10个点有关

      设dp[i][j]就是到i点,然后i,i-1,i-2...i-9的状态为j的方法数,对于状态j有10位0,1,2,3,4,5,6,7,8,9,第k位为1便说明这个地方被圆包含了

      然后右边界的在i上的圆的左边界就不能在i-1-k这个位置,所以就枚举右边界在i+1上的状态向i+1进行转移即可

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N=1e3+11,M=(1<<10)+11;
    const int md=1e9+7,sta1=(1<<10)-1,sta2=(1<<5)-1;
    int dp[N][M],zt1[N],zt2[N];
    vector<int> limr[N];
    //zt1保存该状态的左边界在哪,用来判断能不能向下一个位置转移 
    //zt2保存该状态覆盖了那些点,使得这些点不能作为右边界在下一个位置的圆的左边界 
    void init(){
        for(int i=0;i<=sta2;++i){
            for(int j=0;j<10;j+=2){ 
                if(i&(1<<(j>>1))){
                    zt1[i]|=1<<(j+1);
                    zt2[i]=max(zt2[i],(1<<(j+1))-1);
                }
            }
        }
    }
    int main(){
        init();
        int n,k,x,r;
        scanf("%d%d",&n,&k);
        for(int i=1;i<=k;++i){
            scanf("%d%d",&x,&r);
            limr[x+r].push_back((r-1)<<1);
        }
        dp[0][sta1]=1;
        for(int i=0;i<n;++i){
            //f1同zt1,ff1同zt2 
            int lenv=limr[i+1].size(),f1=0,ff1=0;
            for(int j=0;j<lenv;++j){
                f1|=1<<(limr[i+1][j]+1);
                ff1=max(ff1,(1<<(limr[i+1][j]+1))-1);
            }
            for(int j=0;j<=sta1;++j){
                if(!dp[i][j]) continue;
                int f2=(j<<1)&sta1;
                if(f1&f2) continue;
                for(int k=0;k<=sta2;++k){
                    if(f1&zt1[k]) continue;
                    if(f2&zt1[k]) continue;
                    dp[i+1][ff1|f2|zt2[k]]+=dp[i][j];
                    dp[i+1][ff1|f2|zt2[k]]%=md;
                }
            }
        }
        int ans=0;
        for(int i=0;i<=sta1;++i) ans=(ans+dp[n][i])%md;
        printf("%d
    ",ans);
        return 0;
    }
    圆圆圆

    K - Ragdoll

      题意:一开始n个带有权值a[i]的点,m次操作。op 1:添加一个a[x]=y的点x,op 2:把x点和y点所在的树合并,op 3:修改点x的权值为y。每次操作后,问在有多少在同一树上的点对满足gcd(a[x],a[y])=a[x]^a[y]。

      思路:对于gcd(x,y)=x^y。假设g=gcd(x,y),x%g=0,那么我们可以枚举x的约数g,然后就有y=x^g,此时通过判断gcd(x,y)是否等于g,就可以预处理得到与x满足条件的所有y,而已可以得出对于每个x与之对于的y不超过60个。那么接下来的操作1,2,3就是并查集的按秩合并了,也就是启发式的思想。

      其中预处理可以优化掉一个求gcd的log,

    #include<bits/stdc++.h>
    #include<tr1/unordered_map>
    using namespace std;
    typedef long long ll;
    const int N=3e5+11,M=2e5+11;
    vector<int> vv[M];
    int a[N],fa[N],size[N];
    tr1::unordered_map<int,int> mmp[N];
    tr1::unordered_map<int,int>::iterator it; 
    void init(){
        for(int i=1;i<M;++i){
            for(int j=i<<1;j<M;j+=i){
                int k=(i^j);
                if(__gcd(j,k)==i) vv[j].push_back(k);
            }
        }
    }
    void init2(){
        for(int i=1;i<M;++i){
            for(int j=i<<1;j<M;j+=i){
                if(i+j<M&&(i^j)==i+j){
                    vv[j].push_back(i+j);
                    vv[i+j].push_back(j);
                }
            }
        }
    }
    int find(int x){
        return fa[x]==x ? x : fa[x]=find(fa[x]);
    } 
    int main(){
        init2();
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;++i){
            fa[i]=i;size[i]=1;
            scanf("%d",&a[i]);
            ++mmp[i][a[i]]; 
        }
        ll ans=0;
        int op,x,y,fx,fy;
        while(m--){
            scanf("%d%d%d",&op,&x,&y);
            if(op==1){
                a[x]=y;
                fa[x]=x;
                size[x]=1;
                ++mmp[x][y];
            }else if(op==2){
                fx=find(x),fy=find(y);
                if(fx!=fy){
                    if(size[fx]>size[fy]) swap(fx,fy);
                    for(it=mmp[fx].begin();it!=mmp[fx].end();++it){
                        x=it->first;y=it->second;
                        for(int i=0;i<(int)vv[x].size();++i){
                            if(mmp[fy].count(vv[x][i])) ans+=1ll*y*mmp[fy][vv[x][i]];
                        }
                    }
                    fa[fx]=fy;
                    size[fy]+=size[fx];
                    for(it=mmp[fx].begin();it!=mmp[fx].end();++it){
                        x=it->first;y=it->second;
                        mmp[fy][x]+=y;
                    }
                    size[fx]=0;
                    mmp[fx].clear();
                }
            }else{
                fx=find(x);
                for(int i=0;i<(int)vv[a[x]].size();++i){
                    if(mmp[fx].count(vv[a[x]][i])) ans-=mmp[fx][vv[a[x]][i]];
                }
                --mmp[fx][a[x]];
                for(int i=0;i<(int)vv[y].size();++i){
                    if(mmp[fx].count(vv[y][i])) ans+=mmp[fx][vv[y][i]];
                }
                a[x]=y;
                ++mmp[fx][y];
            }
            printf("%lld
    ",ans);
        }
        return 0;
    }
    合合合
  • 相关阅读:
    poj 2757 : 最长上升子序列(JAVA)
    POJ 2760: 数字三角形
    poj 1664:放苹果
    Poj 2756:二叉树
    poj机上的JAVA那些事儿
    浅谈HASH算法与CSDN密码泄漏事件
    如何防范密码被破解
    [转载自百度文库]数组拷贝(System.arraycopy,深度拷贝)--数组
    Java数学计算
    fzu Problem 1396 Large Caclulating Work
  • 原文地址:https://www.cnblogs.com/LMCC1108/p/13997322.html
Copyright © 2011-2022 走看看