CodeTON Round 8 (Div. 1 + Div. 2, Rated, Prizes!)(A-F)

 A. Farmer John's Challenge

        构建循环右移有序数组,有以下三种情况:

        1、k=n时,我们使n个数都相等即可;

        2、k=1时,除了都相等的情况外随意构建即可;

        3、其他情况无解,因为除了n个数都相等的情况下,一个有序数组的最左端的数一定比最右端的数小,无论如何循环右移都无法构成第二个有序数组。

void solve()
{
    int n,k; cin>>n>>k;
    if(k==1)
    {
        for(int i=1;i<=n;i++) cout<<i<<" "; cout<<"\n";
    }
    else if(k==n)
    {
        for(int i=1;i<=n;i++) cout<<"1 "; cout<<"\n";
    }
    else cout<<"-1\n";
}

 B. Bessie and MEX

        ai = MEX(p1,p2,…,pi)−pi,即a数组每一个数都和他的MEX前缀有关系,所以我们考虑从后往前跑出答案,由于p是一个排列,对于pn的结果我们是可以确定的即pn = n-an,然后我们根据后一个数的确定从而更新前一个数的前缀MEX即可。

int a[N],p[N];
void solve()
{
    int n,i; cin>>n;
    for(i=1;i<=n;i++) cin>>a[i];
    int cnt=n; //当前的MEX
    for(i=n;i>=1;i--)
    {
        p[i]=cnt-a[i];
        cnt=min(cnt,p[i]);
    }
    for(i=1;i<=n;i++) cout<<p[i]<<" "; cout<<"\n";
}

 C1. Bessie's Birthday Cake (Easy Version)

        对于一个n边形随意连边我们可以切割出n-2个三角形,所以题目给出的x个可连接顶点所构成的x边形我们可以把它单独拿出来计算出一部分结果,同时当切割点的两个相邻坐标距离为2时(例如1,3)可以额外切割出一个三角形(1,3,2),将这部分结果加上即可。

int a[N];
void solve()
{
    int n,m,y,i; cin>>n>>m>>y; //m为题目内的x
    for(i=1;i<=m;i++) cin>>a[i];
    sort(a+1,a+1+m);
    int ans=m-2;
    for(i=1;i<m;i++)
        if(a[i+1]-a[i]==2) ans++;
    if(a[1]+n-a[m]==2) ans++; //判断首尾距离
    cout<<ans<<"\n";
}

 C2. Bessie's Birthday Cake (Hard Version)

        这题与C1相比多了一个贪心策略:

        对于两个相邻间隔大于2的点,我们在中间添加一个点可以得到的新三角形至少为两个(内部x边形变为x+1边形,新加的点与旁边的点距离恰好为2),如果新添加的点与两边的点距离都为2,那么我们可以得到3个新三角形(例如1,5中间添加3);

        当两个相邻的点间隔距离小于2时,我们无法添加;

        等于2时,我们添加会破坏原有的外部三角形;

        所以我们需要将相邻距离是奇数的值按大小存起来(最后一次添加点可以得到3个新三角形),偶数的正常添加即可。

int a[N];
void solve()
{
    int n,m,y,i; cin>>n>>m>>y; //m为题目内的x
    for(i=1;i<=m;i++) cin>>a[i];
    sort(a+1,a+1+m);
    int ans=m-2;
    for(i=1;i<m;i++)
        if(a[i+1]-a[i]==2) ans++;
    if(a[1]+n-a[m]==2) ans++;
    priority_queue<int,vector<int>,greater<int>>q;
//优先队列优先访问距离为偶数的,这样可以更快的得到一步切割三个点的操作
    int cnt=0;
    for(i=1;i<m;i++)
    {
        int d=a[i+1]-a[i];
        if(d<=2) continue;
        if(d%2) cnt+=d/2;
        else q.push(d/2-1); //(1,5)中间可以添加一个点
    }
    int d=a[1]+n-a[m];
    if(d%2&&d>2) cnt+=d/2;
    else if(d>2) q.push(d/2-1);
 
    while(y&&!q.empty())
    {
        int t=q.top(); q.pop();
        if(t<=y)
        {
            y-=t;
            ans+=2ll*t+1;
        }
        else
        {
            ans+=2ll*y; y=0;
            break;
        }
    }
    ans+=min(cnt*2,y*2);
    cout<<ans<<"\n";
}

D. Learning to Paint

        我们考虑开二维dp数组,dp[i][j]表示以i为结尾(不一定要选择i)第k大的数。

        所以dp[i][1]=MAX(dp[1][1]+a[3][i],dp[1][2]+a[3][i]...dp[i-1][k])(一定要全选a[3][i]的原因是,只选部分的最大值存储在在后续的dp中),同时因为dp[1][1]是以1为结尾最大值,我们可以优化为:

        dp[i][1]=MAX(dp[1][1]+a[3][i],dp[2][1]+a[4][i]...dp[i-1][1]),这样我们可以使用优先队列维护前(i-1)维没有被继承过的最大值(若dp[1][1]被继承过则塞入dp[1][2]),维护出第i维前k个最大即可。

int a[N][N],dp[N][M]; //dp[i][j]表示以i为结尾第j大的数
struct node
{
    int val,idx,now; //权值val,以idx结尾,第now大
    bool operator < (const node &x) const{
        return val < x.val;
    }
};
void solve()
{
    int n,k,i,j; cin>>n>>k;
    for(i=0;i<=n;i++)
        for(j=0;j<=k;j++)
            dp[i][j]=-inff,a[i][j]=0;
    dp[0][1]=0; //什么都不选时的最大值
    for(i=1;i<=n;i++)
        for(j=i;j<=n;j++)
            cin>>a[i][j];
    for(i=1;i<=n;i++)
    {
        priority_queue<node>q;
        for(j=i-1;j>=0;j--)
        {
            int val=a[j+2][i]+dp[j][1];
            q.push({val,j,1});
        }
        q.push({a[1][i],-1,0}); //相当于从第0维继承
        int cnt=1;
        while(!q.empty())
        {
            auto t=q.top(); q.pop();
            int val=t.val;
            int idx=t.idx;
            int now=t.now;
            if(now>k||cnt>k) continue;
            if(idx==-1)
            {
                dp[i][cnt++]=val;
                continue;
            }
            else
            {
                dp[i][cnt++]=val;
                q.push({dp[idx][now+1]+a[idx+2][i],idx,now+1});
            }
        }
    }
    for(i=1;i<=k;i++) cout<<dp[n][i]<<" "; cout<<"\n";
}

E. Farm Game

        由题可知,若(ai,bi)(1≤i≤n)的距离均为0,那么先手必败,因为后手可以模仿先手的行进直至无法行动,对应的,我们可以扩展到距离均为偶数必败,同样也是模仿直至先手被逼到死角后往回走,后手下一步继续逼近(距离-2),直至距离为0。

        所以我们求出总方案数 - 距离均为偶数的情况再乘以2即可(a牛和b牛调换位置)。

        我们可以将两牛空隙视为桶,将其他空的点视为球,n个相同的球塞进m个不同的桶(桶可空)的方法为C(n+m−1,m−1)​。

#define int long long
void solve()
{
    int l,m; cin>>l>>m;
    int n=l-m*2,i;
    int ans=cmb(n+m*2,m*2); //cmb(n,m)为n里取m
    for(i=0;i<=n;i+=2)
    {
        int now=n-i;
        int cnt=cmb(i/2ll+m-1,m-1)*cmb(now+m,m)%M;
        //将两个数当一个数塞进空隙,使两牛间隔固定为偶数
        ans=((ans-cnt)%M+M)%M;
    }
    cout<<ans*2ll%M<<"\n";
}

 F. Farmer John's Favorite Function

        这道题当一个数最多计算6次后第一个数对最后结果的影响就不会超过1(1e18开六次根的结果为1.91)。

        我们可以分块,每块的答案在前缀答案不同的情况下只会有两种情况(ans和ans+1),所以我们只需要处理出每一块需要得到ans+1情况的前缀最小值即可。

        对于每次修改,都修改该点所在块的结果,遍历每一块并跑完剩余没有分块的点即可。

#define int __int128
#define ll long long
int read() {int w = 0, h = 1;char ch = getchar();while (ch < '0' || ch > '9'){if (ch == '-')h = -h;ch = getchar();}while (ch >= '0' && ch <= '9'){w = w * 10 + ch - '0';ch = getchar();}return w * h;}
void write(int x) { if (x > 9) write(x / 10); putchar(x % 10 + '0');}
void writeln(int x) { write(x); puts(""); }
void writech(int x) { write(x); putchar(' '); }
int a[N],t[N],mi[N],id[N],L[N],R[N];
//t为跑出该块的结果,mi为进入该块跑出结果为t+1的最小值
int n,q,len;
int sqrtt(int k)
{
    int x=sqrt((ll)k);
    while((x+1)*(x+1)<k) x++;
    while((x)*(x)>k) x--;
    return x;
}
void build(int cnt)
{
    int i,x=0;
    int l=L[cnt],r=R[cnt];
    for(i=l;i<=r;i++)
    {
        id[i]=cnt;
        x=sqrtt(x+a[i]);
    }
    t[cnt]=x; x++;
    for(i=r;i>=l;i--)
    {
        x=x*x-a[i];
        if(x>=inff) break;
    }
    mi[cnt]=x;
}
void solve()
{
    int i,j,cnt=0; n=read(); q=read();
    for(i=1;i<=n;i++) a[i]=read();
    len=min((int)100,n);
    for(i=1;i+len<=n;i+=len)
    {
        cnt++;
        L[cnt]=i; R[cnt]=i+len-1;
        build(cnt);
    }
    while(q--)
    {
        int k,x,ans=0;
        k=read(); x=read(); a[k]=x;
        if(id[k]) build(id[k]);
        for(i=1;i<=cnt;i++)
        {
            if(ans>=mi[i]) ans=t[i]+1;
            else ans=t[i];
        }
        for(i=R[cnt]+1;i<=n;i++) ans=sqrtt(ans+a[i]);
        writeln(ans);
    }
}

  • 10
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值