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;
}
合合合
原文地址:https://www.cnblogs.com/LMCC1108/p/13997322.html