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
posted @ 2020-11-10 19:57  xzx9  阅读(1283)  评论(0编辑  收藏  举报