题意
给出一个N(<=100)*N正实数矩阵,保证除最后一行,每一行的最后一个数等于这一行前面的数之和;除最后一列,每一列的最后一个数等于这一列前面的数之和。要求对矩阵里的每一个元素进行上/下取整,使取整后矩阵依然满足该性质且元素和最大。
题解
考虑每行每列建点的经典建图。
源点向每一行连边,下界为该行元素和下取整,上界为该行元素和上取整。
每一列同理向汇点连边。
每一行向每一列连边,下界为该位置的数下取整,上界为该位置的数上取整。
然后跑一下上下界最大流就好。
代码
调了好长时间,发现实数上下取整的时候出现了奇怪的问题…
所以写了一个比较难看的read
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 210
#define M 20500
#define inf 0x3f3f3f3f
using namespace std;
int n,m,l,r,deg[N],S,T,SS,TT,Cnt,ans;
char c[7];
double a;
int to[M],hd[M],val[M],lk[N],cnt=1;
inline int read()
{
scanf("%s",c);
int tp=strlen(c),ret=0;
bool f=0;
for(int i=0;i<tp;i++)
if(c[i]!='.')ret=ret*10+c[i]-'0';
else f=1;
if(!f)ret*=10;return ret;
}
inline void add(int u,int v,int w)
{
to[++cnt]=v,hd[cnt]=lk[u],val[cnt]=w,lk[u]=cnt,
to[++cnt]=u,hd[cnt]=lk[v],lk[v]=cnt;
}
int q[N],h,t,dis[N],cur[N];
bool bfs(bool mode)
{
int x;
for(int i=0;i<=TT;i++)
dis[i]=0,cur[i]=lk[i];
dis[q[h=0]=(mode?S:SS)]=t=1;
while(h<t)
{
x=q[h++];
for(int k,i=lk[x];i;i=hd[i])
if(val[i]&&!dis[k=to[i]])
dis[q[t++]=k]=dis[x]+1;
}
return dis[mode?T:TT];
}
int dfs(int x,int v,int mode)
{
if(x==(mode?T:TT))return v;
int ret=0,cst;
for(int k,&i=cur[x];i;i=hd[i])
if(dis[k=to[i]]==dis[x]+1&&val[i])
{
cst=dfs(k,min(v,val[i]),mode);
v-=cst,ret+=cst,val[i]-=cst,val[i^1]+=cst;
if(!v)break;
}
return ret;
}int u,v;
int main()
{
scanf("%d",&n);T=(S=((n-1)<<1))+1,TT=(SS=T+1)+1;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
{
m=read();
l=m/10,r=(m+9)/10;
if(i<n-1)
{
if(j<n-1)u=i,v=n+j-1;
else u=S,v=i;
}
else if(j<n-1)u=n+j-1,v=T;
else continue;
if(l<r)add(u,v,1);
deg[u]+=l,deg[v]-=l;
}
Cnt=cnt,m=0;
for(int i=0;i<=T;i++)
if(deg[i]<0)
add(SS,i,-deg[i]);
else add(i,TT,deg[i]),m+=deg[i];
add(T,S,inf);
while(bfs(0))ans+=dfs(SS,inf,0);
if(ans<m)
{puts("No");return 0;}
ans=0;
for(int i=lk[SS];i;i=hd[i])
val[i]=val[i^1]=0;
for(int i=lk[TT];i;i=hd[i])
val[i]=val[i^1]=0;
while(bfs(1))ans+=dfs(S,inf,1);
printf("%d",ans*3);
}