题目描述
给定一个 的网格图,每个格子都有一个权值,你需要将这些格子划分为尽可能多的连通块,使得每一个连通块的格子权值的平均值均相等。这里的联通指的是四联通。
分析
首先先将所有格子减去平均值,这样转化为将格子划分为尽可能多的连通块,使得每一块所有格子的和为 。
一个直观的想法是在状态中记录上一列两个格子所在连通块的大小进行 DP,但这是不可接受和不必要的。
如果上一列两个格子所在的连通块不同且两个连通块的权值和都不为 的状态是毫无必要的,因为我们完全可以等到至少有一个连通块的权值和为 的时候再考虑这种转移。
此时保证在已经考虑完的格子中,至多有一个连通块的权值不为 。此时这个连通块的权值也是不必要记录的,因为其它的连通块的和均为 ,则已经考虑完的所有格子的和就是这个连通块的权值。
现在我们把状态精简到 ,表示已经考虑完了前 列的格子,第 行的格子所在的连通块的和不保证为 。不妨记录 为第 行前 个格子的和。
当前讨论的是 的转移,记 ,即另外一行的编号。
- 让一列两个格子全部划入和不一定为 的连通块内,用 来更新。
- 让在另外一行内末尾若干个格子单独成为一个和为 的连通块,用 来更新。
- 在另外一行内末尾若干个格子和先前和不一定为 的连通块拼起来形成一个和为 的连通块,此时会在当前这一行产生一个和不一定为 的连通块,用 来更新。
特殊地,如果 ,则说明此时不保证为 的连通块的和为 ,需要将 和 均更新为 。
使用哈希表或者 std::map
等维护第二种和第三种转移即可做到 或 。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| constexpr int N=2e5+5;using ll=unsigned long long;constexpr int inf=1e9; ll a[N][2],s[N][2];int f[N][2];struct dat{int v;inline dat(){v=-inf;}}; map<ll,dat>mp[4];inline void gmx(dat&x,int y){if(x.v<y) x.v=y;} inline void work() { ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr); int n;ll sum=0;cin>>n; for(int i=0;i<=1;i++) for(int j=1;j<=n;j++) cin>>a[j][i],sum+=a[j][i],a[j][i]*=2*n; for(int i=0;i<=1;i++) for(int j=1;j<=n;j++) a[j][i]-=sum; for(int i=0;i<=1;i++) for(int j=1;j<=n;j++) s[j][i]=s[j-1][i]+a[j][i]; for(int i=1;i<=n;i++) { for(int j=0;j<=1;j++) gmx(mp[j][s[i-1][j]],f[i-1][j^1]),gmx(mp[j|2][s[i-1][j^1]],f[i-1][j]); for(int j=0;j<=1;j++) f[i][j]=max({f[i-1][0],f[i-1][1],mp[j^1][s[i][j^1]].v+1,mp[(j^1)|2][-s[i][j^1]].v+1}); if(s[i][0]+s[i][1]==0) f[i][0]=f[i][1]=max(f[i][0],f[i][1])+1; } cout<<f[n][0]<<"\n"; }
CPP
|