2024-3-24湖南多校部分题解A-E+G
2024-3-24 湖南多校部分题解A-E+G
本场搬自Pacific NorthWest February,24 2024
(便乘一波国科大搬题思路\乐)
链接http://www.acmicpc-pacnw.org/results.htm
A.ABC_String
ABC三个字母一组,从前往后遍历,记录遍历过程中开的最大组数即可。
比如ABABCC
ABAB时刻,开了2组,AB与AB,
ABABC 时有一组AB已经完成,只剩下一组AB。
看代码比较直观。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
//#define int long long
#define mid ((l+r)>>1)
#define lson u<<1,l,mid
#define rson u<<1|1,mid+1,r
#define inf 0x3f3f3f3f
typedef int LL;
const signed maxn=(signed)1e6+5;
inline LL Read(){
char ch=getchar();bool f=0;LL x=0;
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=1;
for(;isdigit(ch);ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
if(f==1)x=-x;return x;
}
int a,b,c,ab,ac,bc,all;
char s[maxn];
signed main()
{
scanf("%s",s);int n=strlen(s);
a=b=c=ab=ac=bc=all=0;int mx=0;
for(int i=0;i<n;++i){
if(s[i]=='A'){
if(bc) --bc,--all;
else if(b) --b,++ab;
else if(c) --c,++ac;
else{
++a;++all;mx=std::max(all,mx);
}
}
else if(s[i]=='B'){
if(ac) --ac,--all;
else if(a) --a,++ab;
else if(c) --c,++bc;
else{
++b;++all;mx=std::max(all,mx);
}
}
else{
if(ab) --ab,--all;
else if(a) --a,++ac;
else if(b) --b,++bc;
else{
++c;++all;mx=std::max(all,mx);
}
}
}
assert(all==0);
printf("%d\n",mx);
return 0;
}
B.ACceptable Seating Arrangements
从小到大把一个数放到其应到的位置。
举几个例子感受一下
起始数组与结束数组
首先把数字\(1\)移动到应该在的位置。
再把数字\(2\)移动到应该在的位置
所以,规律如下
假设我们要移动的值为S,所有小于S的值都已经在应有的位置上了,把这些小于S的值都用X表示。
显然,对每一行而言,X都在最左边,所有非X值都在X右边。因为只有X小于S所以S一定在X相邻的右侧,同理的,S要到达的位置,也一定在X相邻右侧,记这个位置上的数为T
假设从S往后第一个大于T的数记为M(可能不存在,此时N为行末值),M前面的数记为N(可能\(S=N\))
那么
正确性自己想一下应该就明白了。
接下来放代码
点击查看代码
#include<bits/stdc++.h>
#define ll long long
//#define int long long
#define mid ((l+r)>>1)
#define lson u<<1,l,mid
#define rson u<<1|1,mid+1,r
#define inf 0x3f3f3f3f
#define pii std::pair<int,int>
typedef int LL;
const signed maxn=(signed)1e6+5;
inline LL Read(){
char ch=getchar();bool f=0;LL x=0;
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=1;
for(;isdigit(ch);ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
if(f==1)x=-x;return x;
}
int r,c;
int ar[505][505];
pii pos[505],ori[505];
struct msg{
int x1,y1,x2,y2;
msg(){}
msg(int x1_,int y1_,int x2_,int y2_){
x1=x1_;y1=y1_;x2=x2_;y2=y2_;
}
};
std::vector<msg> ans;
void change(int x1,int y1,int x2,int y2){
//printf("%d %d %d %d\n",x1,y1,x2,y2);
ans.push_back(msg(x1,y1,x2,y2));
int c1=ar[x1][y1],c2=ar[x2][y2];
std::swap(ori[c1],ori[c2]);
std::swap(ar[x1][y1],ar[x2][y2]);
}
void solve(int x){
if(ori[x]==pos[x]) return;
int ro=ori[x].first,co=ori[x].second;
int rn=pos[x].first,cn=pos[x].second;
//printf("%d:<%d,%d>-><%d,%d>\n",x,ro,co,rn,cn);
int ct=co+1;
while(ct<=c&&ar[ro][ct]<ar[rn][cn]) ++ct;
//printf("ct=%d\n",ct);
for(int i=ct-1;i>=co;--i) change(ro,i,rn,cn);
}
signed main()
{
r=Read(),c=Read();
for(int i=1;i<=r;++i){
for(int j=1;j<=c;++j){
int x=Read();
ori[x]=pii(i,j);
ar[i][j]=x;
}
}
for(int i=1;i<=r;++i){
for(int j=1;j<=c;++j){
int x=Read();
pos[x]=pii(i,j);
}
}
ans.clear();
for(int i=1;i<=r*c;++i) solve(i);
printf("%d\n",ans.size());
for(auto it:ans) printf("%d %d %d %d\n",it.x1,it.y1,it.x2,it.y2);
return 0;
}
C.Candy factory
我不知道出题人怎么想的,有\(O(1)\)解法的题目开5000的范围而且时限2s。我改成1s了。
我猜这题会被签烂。
随便说一下解法
题目问的是我们要追加多少糖果,那我们直接求我们至少要定做几袋糖果MX。记\(SUM\)为已有的糖果总数。\(ans=MX*K-SUM\)
- 记\(mx\)为单种类糖果中的最大数量。显然要有\(MX>=mx\),不然抽屉原理表明至少有一个袋子里会有重复的糖果。
而如果\(MX>=mx\)显然每一颗糖果都能得到有效利用。
可以把放糖果当做填充\(MX行K列\)的矩阵。要求每一行不出现相同糖果。那从第一列开始,每一列从上往下填充,填充完了放下一列。只要\(mx<=MX\)显然不会出现某一行出现同一种糖果。 - \(MX*K>=SUM\)不能说糖果数太多放不进去。
所以\(MX_{min}=MAX(mx,\lceil SUM/K \rceil)\)
最后输出 \(ans=MX_{min}*K-SUM,O(1)\)复杂度笑拉了。
水一下代码。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
//#define int long long
#define mid ((l+r)>>1)
#define lson u<<1,l,mid
#define rson u<<1|1,mid+1,r
#define inf 0x3f3f3f3f
typedef int LL;
const signed maxn=(signed)1e6+5;
inline LL Read(){
char ch=getchar();bool f=0;LL x=0;
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=1;
for(;isdigit(ch);ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
if(f==1)x=-x;return x;
}
signed main()
{
int n=Read(),k=Read();
ll sum=0,mx=0;
for(int i=1;i<=n;++i){
ll a=Read();mx=std::max(mx,a);
sum+=a;
}
mx=std::max(mx,(sum-1)/k+1);
//printf("%lld %lld\n",mx,sum);
printf("%lld\n",mx*k-sum);
return 0;
}
D.Cramming for Finals
没啥思维含量。
一个人影响的范围最多\(2*d\)列,每一列影响的范围又是一个区间。直接上差分。最多\(4*d*n\)条记录,能过。结束。
不过我记得给的std里面bowen.cpp表现吊打我,大家有兴趣可以看这个std。
放代码。(我代码写的烂,请谅解)
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define int long long
#define mid ((l+r)>>1)
#define lson u<<1,l,mid
#define rson u<<1|1,mid+1,r
#define inf 0x3f3f3f3f
typedef int LL;
const signed maxn=(signed)1e6+5;
const signed maxm=(signed)2e7+5;
inline LL Read(){
char ch=getchar();bool f=0;LL x=0;
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=1;
for(;isdigit(ch);ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
if(f==1)x=-x;return x;
}
int r,c,d,n;
int xr[maxn],yr[maxn];
std::map<int,int> mp;
struct Msg{
int pos,f;
Msg(){}
Msg(int a,int b){
pos=a,f=b;
}
}msg[maxm];
bool operator < (Msg a,Msg b){
if(a.pos!=b.pos) return a.pos<b.pos;
return a.f<a.f;
}
int cnt=0;
signed main()
{
r=Read(),c=Read(),d=Read(),n=Read();
for(int i=1;i<=n;++i){
yr[i]=Read(),xr[i]=Read();
}
for(int i=1;i<=n;++i) mp[(xr[i]-1)*r+yr[i]]=1;
//printf("r=%d c=%d\n",r,c);
for(int i=1;i<=n;++i){
int L=std::max(1LL,xr[i]-d),R=std::min(c,xr[i]+d);
for(int j=L;j<=R;++j){
int len=std::sqrt(d*d-(xr[i]-j)*(xr[i]-j));
int up=std::max(1LL,yr[i]-len);//up<=down
int down=std::min(r,yr[i]+len);
//printf("poi=%d row=%d up=%d down=%d\n",i,j,up,down);
//printf("+%d -%d\n",(j-1)*r+up,(j-1)*r+down+1);
msg[++cnt]=Msg((j-1)*r+down+1,-1LL);msg[++cnt]=Msg((j-1)*r+up,1LL);
}
}
msg[cnt+1]=Msg(r*c+1,0LL);
std::sort(msg+1,msg+1+cnt);
int mn=n,now=0;
int pos,posn=msg[1].pos;
for(int i=1;i<=cnt;++i){
int f=msg[i].f;
pos=posn;
posn=msg[i+1].pos;
now+=f;
//printf("pos=%d posn=%d f=%d now=%d mn=%d,avipos=%d\n",pos,posn,f,now,mn,avi[pos]);
// if(posn!=pos&&posn!=pos+1){
// printf("0\n");exit(0);
// }
if(posn!=pos){
if(!mp[pos]) mn=std::min(now,mn);
}
}
//if(nr!=r||nc!=c) printf("0\n");
printf("%lld\n",mn);
return 0;
}
E.Eccentric Excursion
本场防AK题。我没看std和题解,一开始看错题目白干一下午,后来想出了个大概但是细节全错。拿着官方数据和随机生成数据对拍了一天,拍的头昏眼花才写出来。一看行数是std的两倍,而且也没比std快。直接泪流满面。要是没兴趣看我乱搞的话直接看官方题解吧。
题目翻译
给定一棵树,大小为\(n\)树上的点\(1\) ~ \(n\)编号,再给定一个\(k\),\(1<=k<n<=500\)要求你给出一个字典序最小的,长度为\(n\)的排列P,满足$$\sum_{i\in[1,n-1]}[<P_i,P_{i+1}>\notin E] =K,(E是边集)$$
说人话就是恰好有K个相邻的点对在树上没有连边。
如果不存在输出-1。
我的解法
首先字典序最小直接考虑贪心。
\(n\in[1,500]\)那么\(O(n^3)\)可以接受。
每一位从小到大枚举每一个数,再check如此前缀下是否可行。我们需要\(O(n)\)的check
check思路
考虑固定前缀下能够做到的k的最大值\(k_{max}\)和最小值\(k_{min}\)。任何\(k \in[k_{min},k_{max}]\)都可以得到。
- 证明一下
假设 \(k_{min}\)对应的序列为\(P_{min}\),\(k_{max}\)对应的序列为\(P_{max}\).我们通过一种一次只会使\(k\)最多变动\(1\)的操作将\(P_{min}\)操作到\(P_{max}\),这样操作过程中的所有序列就会包括区间中的所有k。那么合法操作有reverse \(P[1:x]\)或者 reverse \(P[x:n]\)。只有边界\(<P_x,P_{x+1}>\)会变化,其他的都只是移了位置。至于为啥这样可行,大家自己想一下,我已经有一个绝妙的证明方法,只是奈何空间不够~
假设我们在枚举第\(i\)个数,他的值是\(j\),那么\([1,j]\)的点对是否相连已经固定下来了,我们减去其中不相连的点对数量,余下的记为\(k_{i,j}\),要使的\([P_i(j),P_{i+1},...,P_n]\)中可能的k范围\([k_{min},k_{max}] \ni k_{i,j}\)
对于计算K,我的想法是,先考虑\([P_{i+1},...,P_n]\)的K的范围,若是能在凑到\(k_{max}\)的情况中有满足\(<P_i,P_{i+1}> \notin E\)情况的,就\(++k_{max}\),\(k_{min}\)亦然。
那么先考虑\([P_{i+1},...,P_n]\)的K的范围,这些点构成了一个子图(不必连通),只要这个子图不是星型图(一个点连着其他所有点)(单点不算星图,两个相连的点算),也就是任何一个点都有一个其没有连接的点,那么这张图能提供的\(k_{max}=|V|-1(V是点集).\)如果是星型图就是\(k_{max}=|V|-2.\)
对于星型图很好理解\([叶子结点,叶子,...,唯一非叶节点]\)就凑出来\(|V|-2\)了。
对于非星型图,我需要数学归纳法证明,而且是和另一个命题联合证明。
-
数学归纳法部分
-
假设:对于任意非星图,V为其点集,都能找到使\(k=|V|-1\)的序列(第一个命题)。并且,任意非星图,再额外加上一个强制要求出现在序列第一位的点\(J\),只要\(J\)不与此非星图的所有点连有边,那么此点加非星图所构成的序列可以找到\(k=|V|\)的的情况(第二个命题)。
-
归纳奠基:对于n较小的情况1(事实上连通图\(|V|>=4\)才有非星图),可以找到使得\(k=|V|-1的情况\)
-
归纳递推:若\(|V|<=X\)时成立,那么|V|=X+1时也能成立。
先证明第一个命题。由于第二个命题,我挑一个点作\(J\),因为非星图,\(J\)不可能和其他所有点连边,这样剩下的点集大小\(|V|-1<=X,|V|=X+1\)的第一个命题被\(|V|=X\)的第二个命题证明。
接下来证明\(|V|=X+1\)的第二个命题。挑一个点出来记为\(J\),剩下的点可以凑出\(k=|V|-2\),记此时序列为\([P_1,P_2,...,P_{|V|-1}]\)(不包括\(J\)).若\(<J,P_1> \notin E\),直接就解决了。若是\(<J,P_{|V|-1}> \notin E\),直接 reverse \(P[1:|V|-1]\)首尾互换就解决了。
若是没有解决,就比较麻烦,得先引入其它工具。- 和\(J\)直接相连的任意两点之间不会直接相连
证明:树上无环,非常显然。 - \(k=|P|-1\)时,就是任意相邻两点之间都没有连边,只要保持任意两点始终无连边,就可以保持\(k\)不变。
先记\(rear=|P|-1\)接下来的所有操作都会使
\((i>rear)\implies (<J,P_i>\in E)\)若是序列头不与\(J\)相连,直接证毕,结束。
若是序列头与\(J\)相连,reverse\(P[1:rear]\),原序列头就会被移到rear上,但是\(rear\)之后的点都和\(J\)相连,所以\(<P_{rear},P_{rear+1}>\notin E\)。\(k\)保持不变,没有缩小。之后再\(--rear\),仍然满足\((i>rear)\implies (<J,P_i>\in E)\)
只要有一个点满足与点J不相连,子序列\(P[1:rear]\)一直在缩小,那个点迟早被甩到序列头上去,这样\(<J,P_1> \notin E\)满足,第二个命题成立。
归纳递推证明完毕。 - 和\(J\)直接相连的任意两点之间不会直接相连
由以上可得知两个命题正确.
-
请把数学归纳法里的P和J当做局部变量忘记,全局P是答案序列,\(J=P_i\)。
通过第二个命题(第二个命题对于非星图也成立,也就是不全连就\(+1\)),我们已经知道固定序列头的情况下K的最大值 $$k_{max}=[\exists u \in[i+1,n](<J,P_u>\notin E)]+|n-i-1|-[P[i+1:n]为星图]$$
接下来考虑最小值,说实在的,我也没法严谨证明,但我觉得是对的。大家感性理解即可。
- 额外提一下,序列对\(k\)的贡献就是序列里链的数量-1(链就是最长的,满足区间内相邻的点都有连边的区间)
首先为了使\(k\)最小,我们最好一个一个联通块的做,这样总是不劣的。
然后原图就是一个树,一个联通块也只能是一个树,那么我考虑从叶子结点往上伸展,看看能通过连边伸展到什么程度,如果只有一条链直接两个叶子结点伸展相遇就结束了,但要是有分叉,两个或多个叶子结点伸展中在某一点相遇,就比较麻烦,我的考量是选择两个叶子结点伸展的链合并,其他的因为占用不了相交点就只能单做一条链。出于这个考量就可以使用拓扑排序解决。
但是还有一个问题,当我固定\(P_i=J\)时,能否使得不增加\(k\)的情况下,使\(<P_i,P_{i+1}>\in E\),如果可以,\(k_{min}\)保持,否则\(++k{min}\)。
这个问题我是这么考量的,每一个联通块最多有一个点和\(J\)相连,我直接先把\(J\)当做叶子结点挂在相连的点上,再做一遍拓扑,若是答案不增就可以保持。若是所有与\(J\)相连的联通块挂上\(J\)都是使答案增加,那么只能\(++k_{min}\)
到此所有思路解释完毕。
md感觉好冗长,到时候写的肯定是一坨屎山。
事实上我的代码长度是std两倍。泪目了。
我一段一段的放代码。
//建边部分
bool adj[maxn][maxn];//邻接表存边
struct Edge{
int to,nxt;
}edge[maxn<<1];
int head[maxn],ecnt,du[maxn];
void Adde(int u,int v){//再加上前向星存边
edge[++ecnt]=(Edge){v,head[u]};
head[u]=ecnt;++du[u];
}
//加入和删除答案
bool vis[maxn];//1 means in ans,0 in graph
void add(int u){//加入图,从ans中del
--ans_ptr;vis[u]=0;
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(vis[v]) continue;
++du[u];++du[v];
}
}
void del(int u){//从图中del,加入ans
ans[++ans_ptr]=u;vis[u]=1;
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(vis[v]||du[v]==0) continue;
//printf("del %d-%d\n",u,v);
--du[u];--du[v];
}
}
//切分连通块
int blc[maxn],siz[maxn],bcnt;
//blc 点属于哪个联通块,siz 联通块大小 bcnt联通块数目
int vec[maxn][maxn];//记录每块联通块有哪些点
bool dvis[maxn];//记录是否入ans或者计入连通块
void devide(){//把图分为若干连通子图
bcnt=0;
for(int i=1;i<=n;++i) dvis[i]=vis[i];
for(int i=1;i<=n;++i){
if(!dvis[i]) unite(i,++bcnt);
}
}
void unite(int x,int bc){//标记整块连通子图
std::queue<int> q;
q.push(x);dvis[x]=1;
siz[bc]=0;
while(!q.empty()){
int u=q.front();q.pop();
vec[bc][++siz[bc]]=u;
blc[u]=bc;
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(dvis[v]) continue;
dvis[v]=1;q.push(v);
}
}
}
//拓扑排序
int fa[maxn],ch[maxn][2];
//fa就是树上父亲,ch记录有那些点伸展到此点
int tp[maxn];//top_sort
//tp模仿du,但是du属于原图,不能动,所以copy一份
int tops[maxn],tcnt;
//拓扑完的序列
void top_sort(int ver){//ver是联通块下标
std::queue<int> q;
for(int i=1;i<=siz[ver];++i){
int u=vec[ver][i];
tp[u]=du[u];fa[u]=0;
if(tp[u]==1) q.push(u);
}
tcnt=0;
while(!q.empty()){
int u=q.front();q.pop();
tops[++tcnt]=u;
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(tp[v]==0||vis[v]) continue;
--tp[u];--tp[v];fa[u]=v;
if(tp[v]==1) q.push(v);
}
}
}
//求最小值
int solve_tree(){
//将一棵树分为最少的链,数量为temp
//由single_tree()作初始化
int temp=0;
for(int i=1;i<=tcnt;++i){
int v=tops[i];
int u=fa[v];
if(ch[v][1]) ++temp;
else{
if(!ch[v][0]) ch[v][0]=v;
if(i==tcnt) ++temp;
else if(ch[u][1]) ++temp;
else if(ch[u][0]) ch[u][1]=ch[v][0];
else ch[u][0]=ch[v][0];
}
}
return temp;
}
void single_tree(int ver,int &mn,int poi,bool &ex){
//求单个图(树)对k的最小贡献
//ex 就是是否要++Kmin
if(siz[ver]<=2){
++mn;
for(int i=1;i<=siz[ver];++i){
if(adj[poi][vec[ver][i]]) ex=0;
}
return;
}
top_sort(ver);
int poi_adj=-1;//与P_i相连的点
for(int i=1;i<=tcnt;++i){
ch[tops[i]][0]=ch[tops[i]][1]=0;
if(adj[poi][tops[i]]) poi_adj=tops[i];
}
int temp=solve_tree();
mn+=temp;
if(ex&&poi_adj!=-1){
//看看挂P_i是否会增加答案
for(int i=1;i<=tcnt;++i)
ch[tops[i]][0]=ch[tops[i]][1]=0;
ch[poi_adj][0]=poi;//poi_adj下挂一个叶子结点poi
if(solve_tree()==temp) ex=0;
}
}
//求最大值和调用求最小值
void get(int &mx,int &mn,int poi){
if(bcnt==0){
mn=mx=1;return;
}
if(bcnt==1&&siz[1]==1) mx=2-adj[poi][vec[1][1]];
else{
mx=0;
for(int i=1;i<=bcnt;++i) mx+=siz[i];
for(int i=1;i<=n;++i){
if(!vis[i]&&du[i]==mx-1){
--mx;break;
}
}
for(int i=1;i<=n;++i)
if(!vis[i]&&!adj[poi][i])
{++mx;break;}
}
bool ex=1;mn=0;
for(int i=1;i<=bcnt;++i) single_tree(i,mn,poi,ex);
mn+=ex;
}
//主程序
void pfno(){
printf("-1\n");exit(0);
}
void pfans(){
for(int i=1;i<=n;++i) printf("%d%c",ans[i]," \n"[i==n]);exit(0);
}
signed main(){
n=Read(),k=Read()+1;
for(int i=1;i<n;++i){
int u(Read()),v(Read());
adj[u][v]=adj[v][u]=1;
Adde(u,v);Adde(v,u);
}
for(int i=1;i<=n;++i) blc[i]=1;
for(int i=1;i<=n;++i) vec[1][i]=i;
bcnt=1;siz[1]=n;
int mx,mn;
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
if(vis[j]) continue;
if(i>1&&!adj[ans[i-1]][j]) --k;
del(j);devide();
get(mx,mn,j);
if(k>=mn&&k<=mx) break;
if(i>1&&!adj[ans[i-1]][j]) ++k;
add(j);
}
if(ans_ptr!=i) pfno();
}
pfans();
}
完结撒花
A题秒了
B题也没啥水平,但我其实卡了好久
C题也卡了好一会,但是\(O(1)\)多少有点搞笑
D题我跑的比std慢真得泪目。
不知道有没有人注意我代码里面的return \(a.f<a.f\);
E题写的真屎。
加时赛之别人写的G
某个不会用markdown的发了我一份word题解,太帅了。
我给他补补markdown。以下为题解。
由条件4可知,相邻两行存在约束关系,那么考虑DP
令\(f( r_i, c_l, c_r)\)表示从第一行操作到第\(r_i\) 行,且第 \(r_i\) 行中的 1 所在的列号为 \(c_l\) 到 \(c_r\) 时的最少操作数
若直接进行转移, 则时间复杂度为 \(O(r*c^4)\)这显然是不够的。
观察转移过程可发现,每次转移求得都是\(f(r_i – 1, c_l, c_r)\)的二维前缀最小值。 只要我们将该值提前算出来,那么就可以实现\(O(1)\)的转移,此时的时间复杂度为\(O(r * c^2)\),在最坏情况下依然无法通过。
观察可知,原矩阵在转置后所得新矩阵满足条件时,原矩阵也满足条件。那么在 \(c > r\) 时,将原矩阵转置在进行DP即可,此时的时间复杂度为 \(O(max(r, c) * min(r, c) * min(r, c)) = O(r * c * min(r, c))\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
void Sol()
{
int r, c;
cin >> r >> c;
vector<string> matrix(r);
for (int i = 0; i < r; i++)
cin >> matrix[i];
if (r < c)
{
vector<string> trans(c);
for (int i = 0; i < c; i++)
{
trans[i].assign(r, '0');
for (int j = 0; j < r; j++)
trans[i][j] = matrix[j][i];
}
matrix = move(trans);
swap(r, c);
}
vector<vector<int>> dp(c, vector<int>(c, INF));
vector<int> pre(c + 1);
for (int i = 0; i < r; i++)
{
for (int j = 0; j < c; j++)
{
pre[j + 1] = pre[j] + (matrix[i][j] - '0');
}
vector<int> predp = dp[0];
vector<vector<int>> ndp(c, vector<int>(c, INF));
for (int x = 0; x < c; x++)
{
for (int y = x; y < c; y++)
{
predp[y] = min(predp[y], dp[x][y]);
}
int minpre = predp[x == 0 ? 0 : x - 1];
for (int y = x; y < c; y++)
{
minpre = min(minpre, predp[y]);
if (i == 0)
minpre = x == 0 ? 0 : INF;
ndp[x][y] = min(ndp[x][y], pre[x] + (y - x + 1) - pre[y + 1] + pre[x] + pre[c] - pre[y + 1] + minpre);
}
}
dp = move(ndp);
}
int ans = INF;
for (int i = 0; i < c; i++)
ans = min(ans, dp[i][c - 1]);
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
int t = 1;
//cin >> t;
while (t--)
{
Sol();
}
return 0;
}

浙公网安备 33010602011771号