cogs_396_魔术球问题_(最小路径覆盖+二分图匹配,网络流24题#4)

描述


http://cojs.tk/cogs/problem/problem.php?pid=396

连续从1开始编号的球,按照顺寻一个个放在n个柱子上,\(i\)放在\(j\)上面的必要条件是\(i+j\)是一个完全平方数.问做多能放到几号球.

 

分析


cogs是简化版,我在网上找了个完整版的测试数据,要求输出方案...

求最大放几号球不方便,我们考虑枚举最大的球号,计算最少需要多少柱子.

我们对于满足\(j<i\)且\(i+j\)是一个完全平方数的\(i,j\),从\(j\)向\(i\)连一条边.那么我们要用几条路径,将全部点都连成几串.这个路径就相当于是柱子啦.所以这就是一个求最小路径覆盖的问题.

注意!!!是最小路径覆盖!不是最小边覆盖!!!

我们把每个点\(i\)拆成\(i\)和\(i'\),建立二分图,如果\(i,j\)满足条件,就从\(j\)向\(i'\)连一条边.

对于一个路径覆盖中的一条路径,除了路径末端的那个点,也就是一个柱子最下面的那个球,其他点都有一个后继,在二分图中我们让这些点和它们的后继匹配.

这样的话,由于路径数=路径末端点的个数=点的总数-非末端点的个数=点的总数-二分图中的匹配数,所以我们做一下二分图匹配就可以了.

由于随着球号的增大,最少需要的柱子数量是单调不降的,所以可以二分.但是二分的话每次都要重新建图跑最大流,如果顺次枚举的话,每次只需要加一些边,然后在原来的残量网络中继续跑就OK了.

至于输出方案,最后跑一遍最大球号对应的最大流,然后在二分图的右侧找容量是1的边(从左边流过来1的流量,那么右边回去的容量就会+1),把每个球上面的球号记下来,然后输出的时候从1开始,只要当前这个球还没有输出,就从这个球开始向上一直输出.

 

注意:

1.虽然答案最大是1600,但是完全平方数可能不止这么大,例如:\(1000+1500=2500=500^2\).

 

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int maxn=1600+5,opposite=1600,maxm=1000000+5,INF=0x3fffffff;
 5 int n,cnt=1,s,t,max_flow,ans;
 6 int head[maxn<<1],q[maxn<<1],lv[maxn<<1],itr[maxn<<1],match[maxn];
 7 bool issquare[maxn<<1],vis[maxn];
 8 struct edge{
 9     int to,cap,next;
10     edge(){}
11     edge(int to,int cap,int next):to(to),cap(cap),next(next){}
12 }g[maxm];
13 void add_edge(int u,int v,int cap){
14     g[++cnt]=edge(v,cap,head[u]); head[u]=cnt;
15     g[++cnt]=edge(u,0,head[v]); head[v]=cnt;
16 }
17 void bfs(){
18     int front=0,tail=1;
19     q[front]=s;
20     memset(lv,-1,sizeof lv); lv[s]=0;
21     while(front<tail){
22         int u=q[front++];
23         for(int i=head[u];i;i=g[i].next){
24             int v=g[i].to;
25             if(lv[v]<0&&g[i].cap>0){
26                 lv[v]=lv[u]+1;
27                 q[tail++]=v;
28             }
29         }
30     }
31 }
32 int dfs(int u,int f){
33     if(u==t) return f;
34     for(int &i=itr[u];i;i=g[i].next){
35         int v=g[i].to;
36         if(g[i].cap>0&&lv[u]<lv[v]){
37             int d=dfs(v,min(g[i].cap,f));
38             if(d>0){
39                 g[i].cap-=d;
40                 g[i^1].cap+=d;
41                 return d;
42             }
43         }
44     }
45     return 0;
46 }
47 void Dinic(){
48     int flow=0,f;
49     for(bfs();lv[t]>0;bfs()){
50         for(int i=s;i<=t;i++) itr[i]=head[i];
51         while((f=dfs(s,INF))>0) flow+=f;
52     }
53     max_flow+=flow;
54 }
55 void solve(){
56     max_flow=0;
57     for(int i=1;;i++){
58         ans=i-1;
59         add_edge(s,i,1);
60         add_edge(i+opposite,t,1);
61         for(int j=1;j<i;j++)if(issquare[i+j]) add_edge(j,i+opposite,1);
62         Dinic();
63         if(i-max_flow>n) break;
64     }
65     memset(head,0,sizeof head); cnt=1; max_flow=0;
66     for(int i=1;i<=ans;i++){
67         add_edge(s,i,1);
68         add_edge(i+opposite,t,1);
69         for(int j=1;j<i;j++)if(issquare[i+j]) add_edge(j,i+opposite,1);
70     }
71     Dinic();
72 }
73 void print(){
74     int p;
75     printf("%d\n",ans);
76     for(int i=opposite+1;i<=ans+opposite;i++)for(int j=head[i];j;j=g[j].next)if(g[j].cap==1){
77         match[g[j].to]=i-opposite;
78         break;
79     }
80     for(int i=1;i<=ans;i++)if(!vis[i]){
81         p=0;
82         for(int j=i;j;j=match[j]){
83             printf("%d ",j);
84             vis[j]=true;
85         }
86         printf("\n");
87     }
88 }
89 void init(){
90     scanf("%d",&n);
91     s=0; t=3200+1;
92     for(int i=1;i*i<=3200;i++) issquare[i*i]=true;
93 }
94 int main(){
95     init();
96     solve();
97     print();
98     return 0;
99 }
View Code

 

算法实现题 8-4 魔术球问题(习题 8-14)
«问题描述:
假设有 n 根柱子,现要按下述规则在这 n 根柱子中依次放入编号为 1,2,3,1⁄4的球。
(1)每次只能在某根柱子的最上面放球。
(2)在同一根柱子中,任何 2 个相邻球的编号之和为完全平方数。
试设计一个算法,计算出在 n 根柱子上最多能放多少个球。例如,在 4 根柱子上最多可
放 11 个球。
«编程任务:
对于给定的 n,计算在 n 根柱子上最多能放多少个球。
«数据输入:
由文件 input.txt 提供输入数据。文件第 1 行有 1 个正整数 n,表示柱子数。
«结果输出:
程序运行结束时,将 n 根柱子上最多能放的球数以及相应的放置方案输出到文件
output.txt 中。文件的第一行是球数。接下来的 n 行,每行是一根柱子上的球的编号。
输入文件示例
input.txt
4
输出文件示例
output.txt
11
1 8
2 7 9
3 6 10
4 5 11

posted @ 2016-06-11 23:30  晴歌。  阅读(173)  评论(0编辑  收藏  举报