吃个寿司简直过于矫情
这道题目的点在于
di,j
之间的环环相扣 非常难处理
所以说我们就需要一个无脑算法 来解决这种问题
而这个时候 网络流这种建了图就无脑跑的方法就出现了
对于每个点
di,j
我们只需要向
di+1,j、di,j−1
连边 表示选了它就一定要选另外两个点
然后另外两个点也同样进行连边 就能够解决这些复杂的关系了
但是这仍然不足以解决正权点和负权点之间的复杂关系
于是我们就想 如果能把 牺牲正权 当作 负权变小 的代价
岂不是美哉?
于是接下来就要进行一个神奇的建图了
建图
把点分为正权和负权+
S、T
点
(对于
di,i
要进行特殊处理)
正权点全部连向
S
负权点全部连向
具体而言
di,j
的权值如果为正 就连
S−>di,j
边权即为
di,j
的点权
di,j
的权值如果为负 就连
di,j−>T
边权即为
di,j
的点权的相反数
这样保证了边权全部为正 这也是我们分正负点权连边的原因
(对于
di,i
我们提前将选择
i
的花费计算上去 因此点权实际为
对于编号
x
,向
对于
di,j−>di+1,j、di,j−1
的连边 权值为正无穷
对于
di,i−>x
连接边权为正无穷的边 作为触发
mx2
消耗的条件
然后有一个结论
最后的结果=所有正点权之和(
di,i
为减去
xi
之后的点权) - 最小割
最小割的意义
简单理解为
左边的割为 减去的正权
右边的割为 最后的负权
最小割即为 消耗最小的正权代价 使得最后的负权最小
(左边 最小割的正权边 即为不选择的正权点
而右边的负权边(实际上边权为正 只是表达的意思是负权点)即为选择的负权点)
(左边割边 实际上就是说 右边的负权不选,而只有左边没有割的正权边(选了它)右边才会减去相应的负权)
连接正无穷边的意义
从最小割的意义上来讲
当我们选择了
S−>di,j
时
下一条要割的边为
S−>di+1,j
、
di,j−>di+1,j
之间的一条
而
di,j−>di+1,j
边权为正无穷 所以我们一定会选择割
S−>di+1,j
因此能够满足题意
附上参考代码
#include <iostream>
#include <cstdio>
#define x(i) (n*n+i)
using namespace std;
const int inf=987654321;
inline int input()
{
char c=getchar();int o;bool f=0;
while(c>57||c<48)f|=(c=='-'),c=getchar();
for(o=0;c>47&&c<58;c=getchar())o=(o<<1)+(o<<3)+c-48;
return f?-o:o;
}
int S,E,res=0,n,m,c[123];bool mark[1012];
int all=1,star[12345],nxt[45678],ent[45678],len[45678];
void add(int s,int e,int l)
{
nxt[++all]=star[s];
star[s]=all;
ent[all]=e;
len[all]=l;
nxt[++all]=star[e];
star[e]=all;
ent[all]=s;
len[all]=0;
}
inline int p(int a,int b){return (a*n+b-n);}
void BUILD(int n)
{
int nc;
for(int i=1;i<=n;i++)
{
nc=input();
nc-=c[i];
if(nc>0)add(S,p(i,i),nc),res+=nc;
else add(p(i,i),E,-nc);
for(int j=i+1;j<=n;j++)
{
nc=input();
if(nc>0)res+=nc,add(S,p(i,j),nc);
else add(p(i,j),E,-nc);
add(p(i,j),p(i+1,j),inf);
add(p(i,j),p(i,j-1),inf);
}
add(p(i,i),x(c[i]),inf);
}
for(int i=1,nx;i<=n;i++)
if(!mark[nx=c[i]])mark[nx]=1,add(x(nx),E,m*nx*nx);
}
int dis[12345],cnt[12345];
int SAP(int s,int flow)
{
if(s==E)return flow;
int temp,delta=0,ndis=dis[s]-1;
for(int e,bian=star[s];bian;bian=nxt[bian])
if(len[bian]&&(dis[e=ent[bian]]==ndis))
{
temp=SAP(e,min(flow-delta,len[bian]));
delta+=temp;
len[bian]-=temp;
len[bian^1]+=temp;
if(delta==flow||dis[S]>=E)return delta;
}
if(dis[S]>=E)return delta;
if(--cnt[dis[s]]==0)dis[S]=E;
cnt[++dis[s]]++;
return delta;
}
int main()
{
freopen("sushi.in","r",stdin);
freopen("sushi.out","w",stdout);
n=input();m=input();
S=n*n+1001;E=S+1;
for(int i=1;i<=n;i++)c[i]=input();
BUILD(n);
while(dis[S]<E)res-=SAP(S,inf);
printf("%d",res);
}