COGS1873-happiness
[FJOI 2017 四校联训 Round 11]小猪送货
第一题
显而易见,这道题的每个位置决策都会对周围位置的决策产生影响,且需要满足“收益最优”。点数比较多,想到最小割建模。
今天第一次仔细阅读了《浅析一类最小割问题-彭天翼》,为自己总结一下:
1.应用范围:存在多个二元关系,每个二元关系满足“若两个不相同,则增加v的花费”。必要的话,可以通过将权值取反来保证最小割的性质。
2.建模方法:
1)用流量表示花费(这里边权为容量)。
2)赋予当前有向边含义:割S集表示选择甲,割T集表示选乙;或割a表示A选甲,割c表示A选乙,或割 表示B选乙,割d表示B选甲。
3)方便起见,把A、B之间的两条有向边强制权值相同。
3.列方程求解容量。
{
a+b=v1
c+d=v2
a+e+d=v3
b+e+c=v4
}
构造容易表示的一组解。
4.边权权为负的情况。
1)若e为负。改变有向边含义。
2)若a,b,c,d为负。由于a,c和b,d有且只能割一条,所以将容量都扩大到正数,最后在答案上修改。
5.边权变化对答案的影响。
1)不说也罢,边权的改变一定要按照顺序把答案变回来。可以把边权扩大到正,可以为了不丢浮点精度把边权乘上一个数……总之是有细节的。
题解:
按照所述一般情况构图。注意选文理科的时候,有当前位置本身的收益和附加的收益。若按照如上方法直接构图,会出现类似“并联边”的情况。理论上对答案没有影响,但增加了不少常数。所以与源汇点连边时,将总容量下来,最后同意对源汇点连边。
最后,一个点算上连向汇点的边共有5条出边,这是用当前弧优化可以很好的砍常数。
#include<cstdio>
#include<algorithm>
#include<cstring>
#define R register
#define dmin(_a,_b)(_a)<(_b)?(_a):(_b)
#define dmax(_a,_b)(_a)>(_b)?(_a):(_b)
#define get(_x,_y)((_x-1)*M+_y)
#define inf 1<<29
using namespace std;
struct Edge{int to,nex,f;}edge[145000];
int N,M,S,T,delta,et,st[11100],cur[11100];
int level[11100],q[11100],l,r;
int ns[11100],nt[11100],v1[11100],v2[11100],v3[11100],v4[11100];
void read(int &aa)
{
R char ch;while(ch=getchar(),ch<'0'||ch>'9');aa=ch-'0';
while(ch=getchar(),ch>='0'&&ch<='9')aa=aa*10+ch-'0';
}
void add(int a,int b,int c)
{
edge[et]=(Edge){b,st[a],c},st[a]=et++;
edge[et]=(Edge){a,st[b],0},st[b]=et++;
}
bool bfs()
{
memset(level,0,sizeof(level));
l=0,r=1,q[0]=S,level[S]=1;
R int u,v,e;
while(l<r)
{
u=q[l++];
for(e=st[u];e!=-1;e=edge[e].nex)
if(!level[edge[e].to]&&edge[e].f>0)
{
v=edge[e].to;
level[v]=level[u]+1;
q[r++]=v;
if(v==T)return true;
}
}
return false;
}
int dfs(int u,const int flow)
{
if(u==T)return flow;
R int v,f,ret=0,tmp;
for(int &e=cur[u];e!=-1;e=edge[e].nex)
if(level[edge[e].to]==level[u]+1&&edge[e].f>0)
{
v=edge[e].to;
tmp=dmin(flow-ret,edge[e].f);
f=dfs(v,tmp);
edge[e].f-=f,edge[e^1].f+=f,ret+=f;
if(ret==flow)return ret;
}
return ret;
}
void dinic()
{
R int i,tmp=0;
while(bfs())
{
for(i=N*M+3;i>=1;--i)cur[i]=st[i];
tmp+=dfs(S,inf);
}
printf("%d",delta-tmp/2);
}
int main()
{
freopen("nt2011_happiness.in","r",stdin);
freopen("nt2011_happiness.out","w",stdout);
R int i,j,k;
R int a,b,c,d;
R int n1,n2,tmp1;
read(N);read(M);
S=N*M+3,T=N*M+2,delta=0,et=0;
memset(st,-1,sizeof(st));
for(i=1;i<=N;++i)for(j=1;j<=M;++j)read(k),ns[get(i,j)]=-k*2;
for(i=1;i<=N;++i)for(j=1;j<=M;++j)
{
n1=get(i,j);
read(k);nt[n1]=-k*2;
tmp1=dmin(ns[n1],nt[n1]);tmp1=-tmp1;
delta+=tmp1>>1;ns[n1]+=tmp1,nt[n1]+=tmp1;
}
for(i=1;i< N;++i)for(j=1;j<=M;++j)read(v1[get(i,j)]);
for(i=1;i< N;++i)for(j=1;j<=M;++j)read(v2[get(i,j)]);
for(i=1;i<=N;++i)for(j=1;j< M;++j)read(v3[get(i,j)]);
for(i=1;i<=N;++i)for(j=1;j< M;++j)read(v4[get(i,j)]);
for(i=1;i<=N;++i)for(j=1;j<=M;++j)
{
if(i!=N)
{
n1=get(i,j),n2=get(i+1,j);
add(n1,n2,v1[n1]+v2[n1]),add(n2,n1,v1[n1]+v2[n1]);
delta+=v1[n1]+v2[n1];
ns[n1]+=v2[n1],ns[n2]+=v2[n1];nt[n1]+=v1[n1],nt[n2]+=v1[n1];
}
if(j!=M)
{
n1=get(i,j),n2=get(i,j+1);
add(n1,n2,v3[n1]+v4[n1]),add(n2,n1,v3[n1]+v4[n1]);
delta+=v3[n1]+v4[n1];
ns[n1]+=v4[n1],ns[n2]+=v4[n1];nt[n1]+=v3[n1],nt[n2]+=v3[n1];
}
}
for(i=1;i<=N;++i)for(j=1;j<=M;++j)
{
n1=get(i,j);
add(S,n1,ns[n1]);add(n1,T,nt[n1]);
}
dinic();
return 0;
}
第二题
很容易想到最大流的做法,建图如下:
中间的点表示星球,S 向 i星 连容量为 p[i] 的边代表生产的猪粮数量,i星 向 T 连容量为 s[i] 的有向边代表卖出的猪粮数量。中间 i星 向 i+j星(j>0&&i+j<=N) 连容量为 c 的边代表运输。
观察数据范围,发现dinic最好情况下也只能跑过10万条边,而这道题点数1万。用上述 O(n2) 的构图方法时间空间双爆炸,只能拿60分。
考虑优化边数。阅历不深,目前优化边数见过的方法的就是增加中介节点或相邻节点连边。试了又试,这道题似乎优化边数不可做。
既然边数无法优化,我们考虑转化角度,用最小割来观察这个模型,因为可能可以使用DP。
我们发现,如果一个点割了左边,必须使之前割右边的点无法通过中间的边流入汇点,所以必须把这些边割掉。同时我们还注意到,中间边的边权都是确定的,只要知道之前割了多少次右边的边就可以知道割当前中间的费用了。
所以状态转移方程就不难写出了。
fi,j
表示从上到下做到 i 个点,之前割了 j 条右边的边的最小割。有:
fi,j=dmax(fi−1,j−1+rightvaluei,fi−1,j+j×c+leftvaluei)
i 这维可以用滚动数组优化。时间复杂度
O(n2)
,常数很小,轻松跑过。空间复杂度
O(n)
。
用DP来做最小割我是第一次接触,奇妙。
#include<cstdio>
#include<algorithm>
#include<cstring>
#define cmin(_a,_b)(_a)>(_b)?(_a)=(_b):0
#define R register
using namespace std;
int N,C,ans;
int p[10010],s[10010],f[2][10010];
void read(int &aa)
{
R char ch;while(ch=getchar(),ch<'0'||ch>'9');aa=ch-'0';
while(ch=getchar(),ch>='0'&&ch<='9')aa=aa*10+ch-'0';
}
int main()
{
R int i,j,h,tmp;
read(N);read(C);
for(i=1;i<=N;++i)read(p[i]);
for(i=1;i<=N;++i)read(s[i]);
memset(f,63,sizeof(f));
f[0][0]=0,ans=1<<30;
for(h=1,i=1;h<=N;++h,i^=1)
{
for(j=0;j<=h;++j)
{
f[i][j]=f[i^1][j]+j*C+p[h];
if(j>0)cmin(f[i][j],f[i^1][j-1]+s[h]);
if(h==N&&f[i][j]<ans)ans=f[i][j];
}
}
printf("%d",ans);
return 0;
}
the end.