POWOJ 1739: 魔术球问题 DAG最小路径覆盖转最大流
题意:
假设有n根柱子,现要按下述规则在这n根柱子中依次放入编号为1,2,3,...的球。 (1)每次只能在某根柱子的最上面放球。 (2)在同一根柱子中,任何2个相邻球的编号之和为完全平方数。 试设计一个算法,计算出在n根柱子上最多能放多少个球。对于给定的n,计算在n根柱子上最多能放多少个球。
tags: 对大佬来说应该是很素的一道题,但某还是花了好多时间才做出来。。 一开始连建图都有点懵,然后最小路径还是新概念,最大匹配也不太懂,最大流倒是会一点。 然后要打印答案,也不会,或者说最小路径该怎么从网络流中反映出来,这没怎么搞懂。
可以参考 百度文库,讲的很详细。
1、首先要想到怎么建图,然后把最小路径覆盖转化为二分图最大匹配,再化为最大流求解。
2、某是二分答案,每次跑一遍网络流,判断最小路径覆盖数是否小于等于柱子数 。 对于i<j,且 i+j是完全平方数, 建二分图,即连接点 i 和点 m+j 。最后1~m点连接源点,m+1~2*m点连接汇点。 在二分图中求出最大匹配数,DAG中的最小路径覆盖数 = DAG图中的节点数 - 相应二分图中的最大匹配数。 最后搜索打印答案。
3、本来二分应该要比枚举快,但这题,枚举可以利用上次的残余网络,而二分每次要重新建图,反而更慢。
建模分析
由于是顺序放球,每根柱子上的球满足这样的特征,即下面的球编号小于上面球的编号。抽象成图论,把每个球看作一个顶点,就是编号较小的顶点向编号较大的顶点连接边,条件是两个球可以相邻,即编号之和为完全平方数。每根柱子看做一条路径,N根柱子要覆盖掉所有点,一个解就是一个路径覆盖。
// 1739: 魔术球问题 #include<bits/stdc++.h> using namespace std; #pragma comment(linker, "/STACK:102400000,102400000") #define rep(i,a,b) for (int i=a;i<=b;i++) #define per(i,b,a) for (int i=b;i>=a;i--) #define mes(a,b) memset(a,b,sizeof(a)) #define INF 0x3f3f3f3f typedef long long ll; const int N = 100005; int n, tot, dis[N], head[N], cur[N], remain[N<<1], path[N]; bool vis[N]; struct Edge{int to, next;}e[N<<1]; void Addedge(int u,int v,int w) { e[tot]={v,head[u]}; remain[tot]=w; head[u]=tot++; e[tot]={u,head[v]}; remain[tot]=0; head[v]=tot++; //反向流量初始为 0 } bool bfs(int st, int ed) // bfs()查询 s到 t是否还有增广路径 { for(int i=st; i<=ed; i++) cur[i]=head[i]; mes(dis, -1); queue<int > q; q.push(st); dis[st]=0; while(!q.empty()) { int u=q.front(); q.pop(); for(int i=head[u]; i!=-1; i=e[i].next) { int to=e[i].to; if(dis[to]==-1 && remain[i]) { //remain[]<0 也可 q.push(to); dis[to]=dis[u]+1; if(to==ed) return true; } } } return false; } int dfs(int now, int ed, int flow) // flow表示能够提供给当前结点的最大流量 { if(now==ed || flow==0) return flow; int s, ans=0; for(int &i=cur[now]; i!=-1; i=e[i].next) { // &i=cur[now],改变了cur[now],让下一次的dfs不重复走 int to=e[i].to; if(dis[to]==dis[now]+1 && (s=dfs(to,ed,min(flow,remain[i]))) ) { if(to>(ed-1)/2) path[now]=to-(ed-1)/2, vis[path[now]]=1; // 记录路径 ans+=s, flow-=s; // ans表示当前结点可以消耗的最大流量 remain[i]-=s, remain[i^1]+=s; // 更新残余流量 if(flow==0) break; } } return ans; } int dinic(int m) { int st=0, ed=2*m+1, s, ans=0; while(bfs(st,ed)) while(s=dfs(st,ed,INF)) ans+=s; // ans即是最大流,也是二分图最大匹配数 return ans; } int solve(int m) { int st=0, ed=2*m+1; tot=0; mes(head,-1); mes(vis,false); mes(path, 0); rep(i,1,m) rep(j,i+1,m) { //加边,建二分图 if((int)sqrt(i+j)==sqrt(i+j)) Addedge(i,m+j,1); } rep(i,1,m) { Addedge(st,i,1), Addedge(m+i,ed,1); } return m-dinic(m); // DAG中的最小路径覆盖数 = DAG图中的节点数 - 相应二分图中的最大匹配数 // 二分图中,最小边覆盖 = 图中点的个数 - 最大匹配数=最大独立集 , 最大匹配数 = 左边匹配点 + 右边未匹配点 } void print(int ans) // 打印答案 { printf("%d\n", ans); rep(i,1,ans) if(vis[i]==0) { printf("%d", i); int pos=i; while(path[pos]) { printf(" %d", path[pos]); pos=path[pos]; } puts(""); } } int main() { scanf("%d", &n); int l=n, r=2000, ans=0; while(l<=r) { int mid=(l+r)>>1; int ans1=solve(mid); if(ans1<=n) { // 每次跑一遍网络流,判断最小路径覆盖数是否小于等于柱子数 if(ans1==n) ans=max(ans, mid); l=mid+1; } else r=mid-1; } solve(ans); print(ans); return 0; }