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\n",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\n",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\n",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\n");
        else printf("Bob\n");
    }
    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\n",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\n",ans);
    }
    return 0;
}
合合合

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM