题目链接:魔术球问题
【线性规划与网络流24题 4】魔术球
Description
假设有n根柱子,现要按下述规则在这n根柱子中依次放入编号为 1,2,3,...的球。
(1)每次只能在某根柱子的最上面放球。
(2)在同一根柱子中,任何2个相邻球的编号之和为完全平方数。
试设计一个算法,计算出在n根柱子上最多能放多少个球。例如,在4 根柱子上最多可放11个球。
编程任务: 对于给定的n,计算在 n根柱子上最多能放多少个球。
Input
第1 行有 1个正整数n,表示柱子数。(0<n<60)
Output
/*
程序运行结束时,将 n 根柱子上最多能放的球数以及相应的放置方案输出。
文件的第一行是球数。接下来的n行,每行是一根柱子上的球的编号。
按字典序输出方案
*/
一行,包含一个整数,表示最多能放的球数
Sample Input
4
Sample Output
11
/*
1 8
2 7 9
3 6 10
4 5 11
*/
这个题难点在于:题目中建模的转化:跟流量有什么关系吗?
其实是有的:要求最多能放的球数,因为柱子数是固定的,那么:重叠的球数越多越好?!(不是废话吗)
当然不是:用流量的思路来说,就是要求最大流啊
所以主要思路是:枚举答案转化为判定性问题,然后最小路径覆盖,可以转化成二分图最大匹配,从而用最大流解决
第一点:为什么是枚举答案而不是二分?
在网络流中,建图是一个很大的板块(花很多时间和空间)
如果是枚举答案,只需要在原来的图上继续寻找增广路就可以了
如果是二分,那么需要重新建图,会很麻烦
第二点:如何拆点?
因为点数不定(是不断增加的)
那么,就不能采取i,i+n的这种平移形式的拆点
所以,用i<<1和i<<1|1是很好的方法
第三点:最小路径覆盖怎么弄?
把每个球看作一个顶点,就是编号较小的顶点向编号较大的顶点连接边,条件是两个球可以相邻,即编号之和为完全平方数
每根柱子看做一条路径,N根柱子要覆盖掉所有点,一个解就是一个路径覆盖
枚举A的值,当最小路径覆盖数刚好大于N时终止,A-1就是最优解
第四点:如何找方案?
dfs找匹配的点
因为每条边的容量都是1,如果这个点在方案中,那么在数组的flow中是有标记的
另外注意每个点连的,是另外一个点拆点之后的点,需要分清楚是哪一个,dfs一直走到头即可
代码如下:
#include<bits/stdc++.h>
using namespace std;
bool issqr(int x){
int y=int(sqrt(x*1.0));
if (x==y*y) return true;
return false;
}
const int maxn=11000;
const int maxm=1200010;
const int INF=999999;
int n,m,k;
struct Edge{
int to,nxt,cap,flow;
}edge[maxm];
int tol,Head[maxn];
bool flag[maxn];
int path[maxn],num;
void init(){
memset(Head,-1,sizeof(Head));
tol=2;
}
void addedge(int u,int v,int w,int rw=0){
edge[tol].to=v;
edge[tol].cap=w;
edge[tol].flow=0;
edge[tol].nxt=Head[u];
Head[u]=tol++;
edge[tol].to=u;
edge[tol].cap=rw;
edge[tol].flow=0;
edge[tol].nxt=Head[v];
Head[v]=tol++;
}
int Q[maxn],dep[maxn],cur[maxn],sta[maxn];
bool bfs(int s,int t,int n){
int front=0,tail=0;
memset(dep,-1,sizeof(dep[0])*(n+1));
dep[s]=0;
Q[tail++]=s;
while(front<tail){
int u=Q[front++];
for(int i=Head[u];i!=-1;i=edge[i].nxt){
int v=edge[i].to;
if (edge[i].cap>edge[i].flow&&dep[v]==-1){
dep[v]=dep[u]+1;
if (v==t) return true;
Q[tail++]=v;
}
}
}
return false;
}
int dinic(int s,int t,int n){
int maxflow=0;
while(bfs(s,t,n)){
for(int i=0;i<n;i++) cur[i]=Head[i];
int u=s,tail=0;
while(cur[s]!=-1){
if (u==t){
int tp=INF;
for(int i=tail-1;i>=0;i--)
tp=min(tp,edge[sta[i]].cap-edge[sta[i]].flow);
maxflow+=tp;
for(int i=tail-1;i>=0;i--){
edge[sta[i]].flow+=tp;
edge[sta[i]^1].flow-=tp;
if (edge[sta[i]].cap-edge[sta[i]].flow==0) tail=i;
}
u=edge[sta[tail]^1].to;
}
else if (cur[u]!=-1&&edge[cur[u]].cap>edge[cur[u]].flow&&dep[u]+1==dep[edge[cur[u]].to]){
sta[tail++]=cur[u];
u=edge[cur[u]].to;
}
else{
while(u!=s&&cur[u]==-1) u=edge[sta[--tail]^1].to;
cur[u]=edge[cur[u]].nxt;
}
}
}
return maxflow;
}
void getpath(int u){
if (u>2*n) return;
flag[u]=1;
num++;
path[num]=u;
u*=2;
for(int i=Head[u];i!=-1;i=edge[i].nxt){
int v=edge[i].to;
if (!flag[v]&&v&&edge[i].flow)
getpath(v/2);
}
}
int main(){
//freopen("input.txt","r",stdin);
//freopen("output.txt","w",stdout);
int ans=0,s=0,t,maxflow=0;
scanf("%d",&k);
t=10001;n=10002;
init();
while(1){
ans++;
addedge(s,ans<<1,1);
addedge((ans<<1)|1,t,1);
for(int i=1;i<ans;i++)
if (issqr(i+ans))
addedge(i<<1,(ans<<1)|1,1);
maxflow+=dinic(s,t,n);
if (ans-maxflow>k) break;
}
//printf("%d\n",--ans);
printf("%d\n",--ans);
/*
memset(flag,false,sizeof(flag));
for(int i=1;i<=ans;i++)
if (!flag[i]){
num=0;
getpath(i);
for(int j=1;j<=num;j++)
printf("%d%c",path[j],j==num?'\n':' ');
}
*/
return 0;
}