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\) 只含有 A,B 字符串,可以删除 AAB 或BAA 的连续子串,删完剩下的串拼到一起,求最多的删除次数。
\(n\le 10^6\)。
idea
最多删除次数与最小保留字符数同一个意思,考虑用 dp,设 \(f_i\) 表示考虑前 \(i\) 个字符最小保留数,转移方程为:
如果不删:
如果 \([j,i]\) 可删:
现在的问题是如何快速判断一个区间能否删空,朴素的暴力是类似括号匹配一样,单次 \(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\) 只含有 A,B 字符串,交换 \(i\) 和 \(i+1\) 的代价是 \(x_i\),求最小代价使得所有 A 不相邻。
\(n\le 10^6\),A 的数量不超过 B 的数量。
idea
如果知道最终字符串的样子,直接匹配的代价是容易计算的,只需要从左往右移到最靠近自己的即可。
Sol
注意到最终串一定是若干 B 和 AB 拼接而成,但可能最后一个是 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,那么显然第一个 A 和 B 可以组合当且选择的区间不是最短的。
于是这样就可以把单项的绝对值变成整体的绝对值,只需要前缀和维护区间内每个 A 移动最近的奇数位置的距离即可。
转移式:
时间复杂度 \(O(n)\) 。
总结
- 重要观察:最终串一定是若干
B和AB拼接而成,且单独 的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


浙公网安备 33010602011771号