AGC066 题解

[AGC066A] Adjacent Difference

题意

\(n\times n\) 的方格 \(a_{i,j}\),你可以进行最多 \(\frac{1}{2}dn^2\) 次操作,让某个方格加一或减一,要求最后每两个相邻方格之间的差的绝对值至少为 \(d\)

\(n\le 500,d\le 1000,|a_i|\le 1000\)

idea

考虑全是 \(0\) 的极端情况,网格黑白染色,将格子数量少的颜色都加上 \(d\),操作数恰好是 \(\left\lfloor\frac{dn^2}{2}\right\rfloor\)。所以推广到普通情况一定有解。

注意到全是 \(0\) 的情况与全相同的情况一样,猜想可以通过取模操作将值域缩小。

Sol

做多 CF 不由得向最小化操作次数方向想。

对所有格子同时取模 \(2d\),令黑色格子最终变成 \(d\) 的偶数倍,白色格子变成 \(d\) 的奇数倍。这样一定满足相差至少为 \(d\) 的条件。

考虑如果这样的操作次数 \(x>\frac{1}{2}dn^2\) ,那么我们让黑色格子取奇数倍,白色格子偶数倍。

证明一下:如果 \(t=a_{i,j}\mod 2d\)

\(0\le t\le d\) 时,变成奇数倍成花费 \(d-t\),变成偶数倍花费 \(t\)

\(d\le t< 2d\) 时,变成奇数倍成花费 \(t-d\),变成偶数倍花费 \(2d-t\)

注意到变成偶数和奇数相加代价为 \(d\),所有加起来为 \(nd^2\),如果一种代价为 \(x>\frac{1}{2}dn^2\),便有 \(x'=dn^2-x\le \frac{1}{2}dn^2\)

时间复杂度 \(O(n^2)\)

总结

重要观察:奇数倍和偶数倍之间至少差 \(d\)

Code

#include<bits/stdc++.h>
#define ll long long
#define N 505
#define endl "\n" 
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;
ll n,d,m;
ll a[N][N],b[N][N],c[N][N];
int wr(){
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            cout<<c[i][j]+a[i][j]<<" ";
        }
        cout<<endl;
    }
    return 0;
}
int main(){
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    cin>>n>>d;
    m=(d<<1);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            cin>>a[i][j];
            b[i][j]=(a[i][j]%m+m)%(m);
        }
    }
    ll res=d*n*n/2;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if((i+j)&1)c[i][j]=d-b[i][j];
            else {
                if(b[i][j]>m-b[i][j])c[i][j]=(m-b[i][j]);
                else c[i][j]=-b[i][j];
            }
            res-=abs(c[i][j]);
        }
    }
    if(res>=0)return wr();
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if((i+j)&1){
                if(b[i][j]>m-b[i][j])c[i][j]=(m-b[i][j]);
                else c[i][j]=-b[i][j];
            }else c[i][j]=d-b[i][j];
            
            res-=abs(c[i][j]);
        }
    }
    
    return wr();
}

[AGC066B] Decreasing Digit Sums

题意

定义 \(f(x)\) 表示 \(x\) 各数位之和,给定 \(n\),找到一个 \(k\le 10^{10000}\) 满足:\(\forall 1\le i\le n,f(2^ik)<f(2^{i-1}k)\)

\(n\le 50\)

idea

一眼乱搞题,并且 \(n\) 没意义,感觉是找什么循环节,让每次增加的小于减少的。

注意到 \(1\sim4\) 第一次答案必然增大,猜测关键在于 \(0,5\sim9\)

注意到 \(5\)\(0\) 的特殊性,\(5\times 2=10\),而 \(0\) 不会变大,所以答案可能是尽量凑 \(0\),然后删掉后面的 \(0\) 的影响。

Sol

\(f(5^x 2^i)=f(5^{x-i}10^i)=f(5^{x-i})\)

于是可以构造 \(f(2\times\overline{5^x5^{x-1}\cdots5^25})= f(\overline{5^{x-1}5^{x-2}\cdots5^15^0})\),每次减少 \(5^x\),后面的 \(1\) 每次 \(\times 2\) 变大,只需要在前面堆一堆很大的 \(5^x\),这样减少的就比增加的多了。

总结

重要观察:\(f(5^x 2^i)=f(5^{x-i}10^i)=f(5^{x-i})\)

Code

#include<bits/stdc++.h>
#define ll long long
#define N 200005
#define endl "\n" 
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;

ll n=0;
vector<ll>v,ans;
void calc(){
    ll t=0;
    for(int i=0;i<n;i++){
        t+=v[i]*5;
        v[i]=t%10;
        t/=10;
    }
    while(t){
        v.push_back(t%10);
        t/=10,n++;
    }
}
void ins(){
    for(auto y:v)ans.push_back(y);
}
int main(){
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    n=1;
    v.push_back(1);
    calc();
    for(auto y:v)cout<<y;
    while(ans.size()+n<=10000)ins(),calc();
    reverse(ans.begin(),ans.end());
    for(auto y:ans)cout<<y;
    return 0;
}

[AGC066C] Delete AAB or BAA

题意

一个长为 \(n\) 只含有 AB 字符串,可以删除 AABBAA 的连续子串,删完剩下的串拼到一起,求最多的删除次数。

\(n\le 10^6\)

idea

最多删除次数与最小保留字符数同一个意思,考虑用 dp,设 \(f_i\) 表示考虑前 \(i\) 个字符最小保留数,转移方程为:

如果不删:

\[f_i=f_{i+1}+1 \]

如果 \([j,i]\) 可删:

\[f_i=\min(f_i,f_{j-1}) \]

现在的问题是如何快速判断一个区间能否删空,朴素的暴力是类似括号匹配一样,单次 \(O(n)\),总复杂度 \(O(n^3)\)

Sol

区间 \([j,i]\) 可删的充要条件是:

  • \(s_i\neq s_j\)
  • \(suma_{i}-suma_{j-1}=2sumb_{i}-2sumb_{j-1}\)

必要性显然,充分性不难证明:

考虑开头为 A,结尾为 B,长为 \(3n\) 的区间,不算开头里面有 \(2n-1\)A, 现在给 \(n\) B 前面任意分配 A,至少有一个空可以分到 \(\left\lceil\frac{2n-1}{n}\right\rceil=2\)\(A\),于是一定可以消掉一次,然后接下来就变成子问题。

所以只需要开个桶分开记录一下前缀和为 \(suma_{i}-2sumb_{i}\),下一个字符是\(s_i+i\)\(f_i\) 最小值即可。

总结

重要观察:区间 \([j,i]\) 可删的充要条件。

Code

#include<bits/stdc++.h>
#define ll long long
#define N 3000005
#define endl "\n" 
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;

ll n;
string s;
ll sum[N],a[N];
ll pos[N][2],f[N];
void sol(){
    cin>>s;
    n=s.size();
    s=" "+s;
    sum[0]=n*2;
    for(int i=1;i<=n;i++){
        a[i]=(s[i]=='A');
        sum[i]=sum[i-1]+(a[i]?1:-2);
        f[i]=inf;
    }
    for(int i=0;i<=3*n;i++)pos[i][0]=pos[i][1]=inf;
    pos[n*2][a[1]^1]=0;
    for(int i=1;i<=n;i++){
        f[i]=f[i-1]+1;
        f[i]=min(f[i],pos[sum[i]][a[i]]);
        pos[sum[i]][a[i+1]^1]=min(pos[sum[i]][a[i+1]^1],f[i]);
 
    }
    cout<<(n-f[n])/3<<endl;
}
int main(){
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    ll ttt;
    cin>>ttt;
    while(ttt--)sol();
    return 0;
}

[AGC066D] A Independent Set

题意

一个长为 \(n\) 只含有 AB 字符串,交换 \(i\)\(i+1\) 的代价是 \(x_i\),求最小代价使得所有 A 不相邻。

\(n\le 10^6\)A 的数量不超过 B 的数量。

idea

如果知道最终字符串的样子,直接匹配的代价是容易计算的,只需要从左往右移到最靠近自己的即可。

Sol

注意到最终串一定是若干 BAB 拼接而成,但可能最后一个是 A,所以末尾增加一个无代价的 B 来统一。

单独的 \(B\) 的位置一定没有发生交换,否则与之交换的 A 放到左边或右边比跨过该点更优。

考虑 dp,设 \(f_i\) 表示前 \(i\) 个位置已经匹配的最小代价,如果是 \(s_i=B\),那么可以不用管,\(f_i=f_{i-1}\)。然后考虑选择右端点为 \(i\) ,选择合法左端点使代价最小。

\(w(l,r)\) 表示将区间 \([l,r]\) 变成 ABABAB 的形式的最小代价,那么区间 \([j+1,i]\) 如果满足 \(cnt_A=cnt_B\),那么有\(f_i=\min\limits_{j=1}^i f_j+w(j+1,i)\)

考虑优化,先令 A 的权值为 \(1\),B 的权值为 -1,记 \(c_i\) 表示前缀和。

\(c_l=c_{mid}=c_r\),则有 \(w(l+1,r)=w(l+1,mid)+w(mid+1,r)\),那么此时注意到 \(f_{mid}+w(mid+1,r)=f_{l}+w(l+1,mid)++w(mid+1,r)=f_{l}+w(l+1,mid)\),所以等效于每次取最靠右的转移即可,转移数变为 \(O(n)\)

考虑如何快速计算 \(w(l,r)\),注意到每个区间,A 都在奇数位,最优的代价一定是从左到右每个 \(A\) 依次连向当且未匹配的最靠左的位置。记 \(f(l,r)\) 表示从 \(l\) 移到 \(r\) 的代价,这是个简单的前缀和。

但是这样还不能简便计算,因为有的向左有的向右,\(f(l,r)\) 有正有负,计算答案带绝对值。

由于我们每次取最靠近的计算,所以所有 A 一定是同向移动的,证明不难:

考虑如果两个 A 相向移动,那么中间必然是相隔了至少三个 B,那么显然第一个 AB 可以组合当且选择的区间不是最短的。

于是这样就可以把单项的绝对值变成整体的绝对值,只需要前缀和维护区间内每个 A 移动最近的奇数位置的距离即可。

转移式:

\[f_{i}=\begin{cases} f_{i-1} & \text{ if } s_i=B \\ f_{j}+|sum(l+1,r)-{odd}(l+1,r)|& \text{ if } j\equiv 0\pmod2\\ f_{j}+|sum(l+1,r)-{even}(l+1,r)|& \text{ if } j\equiv 1\pmod2 \end{cases}\]

时间复杂度 \(O(n)\)

总结

  • 重要观察:最终串一定是若干 BAB 拼接而成,且单独 的 B 没有发生交换。
  • 重要观察:同向移动可以去掉绝对值。
  • dp 的套路和上一道有点像。

Code

#include<bits/stdc++.h>
#define ll long long
#define N 1000005
#define endl "\n" 
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;

ll n;
string s;
ll c[N],a[N],f[N];
ll sum[N];
ll g[N][2],h[N],pos[N*2];
void sol(){
    cin>>n>>s;
    s=" "+s+'B';
    n++;
    c[0]=n;
    f[n+1]=inf;
    for(int i=1;i<=n;i++){
        f[i]=inf;
        g[i][0]=g[i][1]=0;
        h[i]=sum[i]=0;
        pos[i]=pos[i+n]=n+1;
        c[i]=c[i-1]+(s[i]=='A'?1:-1);
    }
    pos[0]=n+1,pos[n]=0;
    for(int i=1;i<n-1;i++){
        cin>>a[i];
        sum[i]=sum[i-1]+a[i];
    }
    sum[n-1]=sum[n-2];
    for(int i=1;i<=n;i++){
        h[i]=h[i-1];
        g[i][1]=g[i-1][1],g[i][0]=g[i-1][0];
        if(s[i]=='A')h[i]=h[i]+sum[i-1];
        g[i][i&1]+=sum[i-1];
    }
    for(int i=1;i<=n;i++){
        if(s[i]=='B')f[i]=f[i-1];
        ll j=pos[c[i]];
        f[i]=min(f[i],f[j]+abs(h[i]-h[j]-g[i][j&1^1]+g[j][j&1^1]));
        pos[c[i]]=i;
    }
    cout<<f[n]<<endl;
}
int main(){
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    ll ttt;
    cin>>ttt;
    while(ttt--)sol();
    return 0;
}

[AGC066E] Sliding Puzzle On Tree

\[\huge \text{{M}{\color{f11111}{yee}}: Stop doing useless Atcoder Grand Contest after the 40th,} \]

\[\huge \text{go and solve some problems,} \]

\[\huge \text{learn how to do IOI2022 National Team homework on Atcoder.} \]

\[\small \text{{M}{\color{f11111}{yee}}: 做不出来开摆了。} \]

posted @ 2024-09-27 14:34  yshpdyt  阅读(39)  评论(0)    收藏  举报