2020 CCPC 長春題解


題目鏈接:https://codeforces.com/gym/102832

題解:https://zhuanlan.zhihu.com/p/279287505

A. Krypton

分析

除獎勵外,其它的倍率均為 \(10\),因此只要求出獎勵的最大值即可,直接 \(0/1\) 背包。

代碼

#include <bits/stdc++.h>

using namespace std;
const int N=2010;
int dp[9][N];
int a[8]={0,8,18,28,58,128,198,388};
int cost[8]={0,1,6,28,88,198,328,648};
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=7;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if(j>=cost[i])
                dp[i][j]=max(dp[i-1][j-cost[i]]+a[i],dp[i-1][j]);
            else
                dp[i][j]=dp[i-1][j];
        }
    }
    printf("%d\n",n*10+dp[7][n]);
    return 0;
}

E. Defense of Valor League

分析

對抗博弈。

代碼

H. Combination Lock

分析

二分圖博弈模板題,每次變換必然會導致當前數字的數位之和的奇偶性改變,按照奇偶性將點分為兩類。其中源點連偶數點,匯點連奇數點,偶數點連奇數點,同時避免不能出現的數。先不把起始點加入,跑一遍,再把起始點加入,跑一遍,通過判斷第二遍的匹配數是否為 \(0\) 來判斷勝者。

代碼

#include <bits/stdc++.h>
#define pb push_back
using namespace std;
//關鍵在於判斷起始狀態是否是最大匹配的必須點
//先求一遍最大匹配,然后把起點刪除再求一遍最大匹配,比較兩次的結果
const int N=1e5+100;
const int inf=0x3f3f3f3f;
int vis[N];
int fac[6];
struct node
{
    int to,val,rev;
};
vector<node>pic[N];
queue<int>que;
int layer[N],iter[N],maxn,st,n,m,start,eds;
void read(int &x)
{
    x=0;
    int f=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-')
            f=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        x=(x<<3)+(x<<1)+ch-'0';
        ch=getchar();
    }
    x*=f;
}
int sum(int x)
{
    int res=0;
    while(x)
    {
        res+=(x%10);
        x/=10;
    }
    return res;
}
void addedge(int u,int v,int w)
{
    pic[u].pb(node{v,w,pic[v].size()});
    pic[v].pb(node{u,0,pic[u].size()-1});
}
void init(int flag)
{
    for(int i=0;i<=eds;i++)
        pic[i].clear();
    for(int i=0;i<=maxn;i++)
    {
        if(vis[i]==flag) continue;
        if(sum(i)&1)
        {
            if(i!=st) addedge(i,eds,1);
        }
        else
        {
            if(i!=st) addedge(start,i,1);
            for(int j=1;j<=m;j++)
            {//偶數向奇數連邊,偶數連源點,奇數連匯點
                int y=i,x=i;
                y/=fac[j-1];
                y%=10;//寫錯了,debug好久...
                x-=y*fac[j-1];
                int t=(y+1)%10;
                int d=x+fac[j-1]*t;
                addedge(i,d,1);
                t=(y-1+10)%10;
                d=x+fac[j-1]*t;
                addedge(i,d,1);
            }
        }
    }
}
bool bfs()
{
    while(!que.empty())
        que.pop();
    for(int i=0;i<=eds;i++)
        layer[i]=-1;
    layer[start]=0;
    que.push(start);
    while(!que.empty())
    {
        int now=que.front();
        que.pop();
        for(auto tmp:pic[now])
        {
            if(layer[tmp.to]<0&&tmp.val>0)
            {
                layer[tmp.to]=layer[now]+1;
                que.push(tmp.to);
                if(tmp.to==eds)
                    return true;
            }
        }
    }
    return false;
}
int dfs(int u,int w)
{
    if(u==eds||w==0)
        return w;
    for(int &i=iter[u];i<pic[u].size();i++)
    {
        node &tmp=pic[u][i];
        if(layer[tmp.to]>layer[u]&&tmp.val>0)
        {
            int d=dfs(tmp.to,min(w,tmp.val));
            if(d>0)
            {
                tmp.val-=d;
                pic[tmp.to][tmp.rev].val+=d;
                return d;
            }
        }
    }
    return 0;
}
int dinic()
{
    int max_flow=0;
    while(bfs())
    {
        for(int i=0;i<=eds;i++)
            iter[i]=0;
        int d=0;
        while((d=dfs(start,inf))>0)
            max_flow+=d;
    }
    return max_flow;
}
int main()
{
    int T;
    read(T);
    fac[0]=1;
    for(int i=1;i<=5;i++)
        fac[i]=fac[i-1]*10;
    while(T--)
    {
        read(m),read(n),read(st);
        maxn=1;//點數
        for(int i=1;i<=m;i++) maxn*=10;
        maxn--;
        start=fac[m];
        eds=start+1;
        for(int i=1;i<=n;i++)
        {
            int d;
            read(d);
            vis[d]=T+1;
        }
        init(T+1);
        int ans=dinic();//先不把起始點加入,跑一遍dinic
        if(sum(st)&1) addedge(st,eds,1);
        else addedge(start,st,1);
        ans=dinic();//再把點加入,看還能不能流
        if(ans==0) printf("Bob\n");
        else printf("Alice\n");
    }
    return 0;
}

K. Ragdoll

分析

對於一個數 \(x\) ,其和另一個數可能的 \(gcd\) 的個數為其約數的個數,而且 \(gcd\) 必然為其約數中的一個。假設 \(x\) 的一個約數為 \(g\) ,那么有 \(gcd(g,x)=g=x \bigoplus y\),所以 \(y=x\bigoplus g\),接下來只要再驗證一下 \(gcd(x,y)=g\) 即可求出與 \(x\) 滿足條件的每個 \(y\) 的值。這個過程可以借助埃式篩在 \(O(n\log^2n)\) 的時間內完成。

接下來用 \(\text{unorder_map}\) 來維護每棵樹中點權的出現次數,每棵樹用並查集維護一個樹根。在兩棵樹合並的時候,將小的樹向大的樹合並,並且修改答案。修改點權時,先將原來的點權的點對減掉,再加上新的點權的貢獻。啟發式合並,復雜度:\(O(n\log n)\)

然后,就是二維 \(\text{unorder_map}\) 的用法。

代碼

#include <bits/stdc++.h>
#define pb push_back
using namespace std;
typedef long long ll;
const int N=1e5+5;
int a[N*3],fa[N*3];
unordered_map<int,int>mp[N*3];
vector<int>num[N*2];
ll ans;
int gcd(int x,int y)
{
    return y?gcd(y,x%y):x;
}
void init()
{
    int maxn=2e5;//埃式篩預處理出每個數的因數,並判斷是否有滿足條件的數對
    for(int i=1;i<=maxn;i++)
    {
        for(int j=i+i;j<=maxn;j+=i)
        {
            int t=(i^j);
            if(t>0&&t<=maxn&&gcd(t,j)==i)
                num[j].pb(t);
        }
    }
}
int Find(int x)//並查集維護是否在同一顆樹上
{
    if(x!=fa[x])
        return fa[x]=Find(fa[x]);
    else return x;
}
void join(int x,int y)
{
    int fx=Find(x);
    int fy=Find(y);
    if(fx==fy) return;
    if(mp[fx].size()>mp[fy].size())//默認x為小樹,y為大樹
        swap(fx,fy);
    for(auto i:mp[fx])//枚舉mp[fx]出現的點權
    {
        for(auto j:num[i.first])//枚舉與mp[fx][i]滿足條件的值出現在y中值
        {
            if(mp[fy].count(j))
                ans+=1LL*i.second*mp[fy][j];
        }
    }
    for(auto i:mp[fx])//樹的合並
        mp[fy][i.first]+=i.second;
    fa[fx]=fy;
}
void update(int x,int y)
{
    int fx=Find(x);
    for(auto i:num[a[x]])
        ans-=mp[fx][i];
    mp[fx][a[x]]--;
    for(auto i:num[y])
        ans+=mp[fx][i];
    mp[fx][y]++;
    a[x]=y;
}
int main()
{
    int n,m;
    ans=0;
    init();
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        fa[i]=i;
        mp[i][a[i]]=1;
    }
    int op,x,y;
    while(m--)
    {
        scanf("%d%d%d",&op,&x,&y);
        if(op==1)
        {
            a[x]=y;
            fa[x]=x;
            mp[x][a[x]]=1;
        }
        else if(op==2)
            join(x,y);
        else
            update(x,y);
        printf("%lld\n",ans);
    }
    return 0;
}

L. Coordinate Paper

分析

首先,只要確定了 \(a_i\) ,就可以確定 \((\sum_{i=1}^{n}{a_i})\mod (k+1)\) 的值。因為,如果不考慮 \(a_i-a_{i+1}=k\) 的情況,有:

\[\sum_{i=1}^{n}{a_i}=a_1\times n+\frac{(n-1)\times n}{2} \]

再考慮 \(a_i-a_{i+1}=k\) 的情況,就在上式的基礎上減去了 \(x\times (k+1)(x\geq 0)\)

即:

\[\sum_{i=1}^{n}{a_i}=a_1\times n+\frac{(n-1)\times n}{2}-x\times(k+1)(x\geq 0) \]

因此,對於一個確定的 \(a_1\% (k+1)\) ,可以在 \(O(1)\) 的時間內確定 \(\sum_{i=1}^{n}{a_i}\mod (k+1)\) 的值,此時的序列一定為如下形式:

\[a_1,a_1+1,k,\cdots,0,1,2,\cdots,k,0,1,2,\cdots \]

此時,對於一個確定的 \(a_1\) ,該序列有 \(\min\sum_{i=1}^{n}{a_i}\)

如果滿足:

\[S\geq \min \sum_{i=1}^{n}{a_i} 且 S\% (k+1)=\sum_{i=1}^{n}{a_i}\%(k+1) \]

則必然可以構造出滿足要求的序列。

因為可以不斷地在滿足 \(\sum_{i=1}^{n}{a_i}\) 最小的序列中不斷的加 \((k+1)\) ,使得最后的和為 \(S\)。加的時候,先對 \(0\) 加,不夠再對 \(1\) 加,以此類推,因為要滿足題目給的相鄰兩個數的大小條件。

代碼

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int N=1e5+5;
ll a[N];
int num[N];
int main()
{
    int n,k,f=0;
    ll s,tmp=0;
    scanf("%d%d%lld",&n,&k,&s);
    if(n==1)
    {
        printf("%lld\n",s);
        return 0;
    }
    for(int i=0;i<=k;i++)
    {
        ll d=1LL*k*(k+1)/2;
        ll m=(n-(k-i+1))/(k+1);//完整區間的個數
        int x=n-(k-i+1)-m*(k+1);//后面剩余部分的個數
        tmp=1LL*(i+min(k,i+n-1))*(min(k,i+n-1)-i+1)/2+d*m;
        if(x>0)
            tmp+=1LL*(0+x-1)*x/2;
        if(s>=tmp&&s%(k+1)==tmp%(k+1))
        {
            f=1;
            a[1]=i;
            break;
        }
    }
    if(f==0)
        printf("-1\n");
    else
    {
        num[a[1]]++;
        for(int i=2;i<=n;i++)
        {
            a[i]=(a[i-1]+1)%(k+1);
            num[a[i]]++;
        }
        ll res=0,y=(s-tmp)/(k+1);
        ll r=y%n,w=y/n;
        int p=-1;
        for(int i=0;i<=k;i++)
        {
            res+=num[i];
            if(res>r)
            {
                p=i;
                res=r-(res-num[i]);
                break;
            }
        }
        for(int i=1;i<=n;i++)
        {
            if(a[i]<p) a[i]+=1LL*(k+1)*(w+1);
            else if(a[i]>p) a[i]+=1LL*(k+1)*w;
            else if(a[i]==p)
            {
                if(res>0) a[i]+=1LL*(k+1)*(w+1),res--;
                else a[i]+=1LL*(k+1)*w;
            }
        }
        for(int i=1;i<=n;i++)
            printf("%lld%c",a[i],i==n?'\n':' ');
    }
    return 0;
}
//10 2 55
//2 10 20


免責聲明!

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



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