AT_agc061_d [AGC061D] Almost Multiplication Table。
大体思路:二分答案然后构造可行解。
利用一个思想就是题目如果有两个变量令一个大于另一个,可以进行分类讨论。
我们先进行二分答案,判断这个 能不能被构造。我们计算每一个 可以的范围 。
观察到 和 很小,所以我们尝试循环构造,进行调整。
我们先假设 (分情况讨论,之后再反过来),这样能保证小的那一个在根号级别。
-
从前往后调整 ,如果比 就变成 ,然后和每一个 取 。
-
从后往前调整 ,如果比 就变成 ,然后和每一个 取 。
合法或无解就可以结束了。怎么证明时间复杂度正确性?
设 ,首先 ,所以 增加的次数 , 减少的次数 。所以时间复杂度就是正确的了。
代码中由于 和 不同,所以需要交换 和 再做一次。
注意细节,特别是复制代码的时候忘改的地方。
代码
C++
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m;
int a[11][11],L[11][11],R[11][11],x[11],y[11];
bool ck(){
for(int i=2;i<=n;i++)if(x[i]<=x[i-1])return 0;
for(int i=2;i<=m;i++)if(y[i]<=y[i-1])return 0;
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
if(x[i]*y[j]>R[i][j]||x[i]*y[j]<L[i][j])return 0;
}
return 1;
}
bool check(int mid){
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
L[i][j]=a[i][j]-mid,R[i][j]=a[i][j]+mid;
}
y[m+1]=2e9+1;
for(int i=1;i<=n;i++)x[i]=1;
for(int i=1;i<=m;i++)y[i]=2e9;
while(1){
for(int i=1;i<=n;i++){
x[i]=max(x[i],x[i-1]+1);
for(int j=1;j<=m;j++)x[i]=max(x[i],(int)ceil(1.0*L[i][j]/y[j]));
// for(int j=1;j<=m;j++)x[i]=max(x[i],((L[i][j]+y[j]-1)/y[j]));
}
for(int i=m;i>=1;i--){
y[i]=min(y[i],y[i+1]-1);
for(int j=1;j<=n;j++)y[i]=min(y[i],R[j][i]/x[j]);
}
// cout<<x[n]<<' '<<y[m]<<endl;
if(x[n]>y[m]||y[1]<=0)break;
if(ck())return 1;
}
x[n+1]=2e9+1;
for(int i=1;i<=n;i++)x[i]=2e9;
for(int i=1;i<=m;i++)y[i]=1;
while(1){
for(int i=1;i<=m;i++){
y[i]=max(y[i],y[i-1]+1);
for(int j=1;j<=n;j++)y[i]=max(y[i],(int)ceil(1.0*L[j][i]/x[j]));
// for(int j=1;j<=n;j++)y[i]=max(y[i],((L[j][i]+x[j]-1)/x[j]));
}
for(int i=n;i>=1;i--){
x[i]=min(x[i],x[i+1]-1);
for(int j=1;j<=m;j++)x[i]=min(x[i],R[i][j]/y[j]);
}
if(x[n]<y[m]||x[1]<=0)break;
if(ck())return 1;
}
return 0;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)cin>>a[i][j];
int l=0,r=1e9;
while(l<r){
int mid=l+r>>1;
if(check(mid))r=mid;
else l=mid+1;
}
check(l);
cout<<l<<endl;
for(int i=1;i<=n;i++)cout<<x[i]<<' ';
cout<<endl;
for(int i=1;i<=m;i++)cout<<y[i]<<' ';
cout<<endl;
return 0;
}