問題描述:
假設有n根柱子,現要按下述規則在這n根柱子中依次放入編號爲1,2,3,...的球。
(1)每次只能在某根柱子的最上面放球。
(2)在同一根柱子中,任何2個相鄰球的編號之和爲完全平方數。
試設計一個算法,計算出在n根柱子上最多能放多少個球。例如,在4 根柱子上最多可
放11 個球。
編程任務:
對於給定的n,計算在n根柱子上最多能放多少個球。
數據輸入:
由文件input.txt提供輸入數據。文件第1 行有1個正整數n,表示柱子數。
結果輸出:
程序運行結束時,將n 根柱子上最多能放的球數以及相應的放置方案輸出到文件
output.txt中。文件的第一行是球數。接下來的n行,每行是一根柱子上的球的編號。
輸入示例 :
4
輸出示例 :
11
1 8
2 7 9
3 6 10
4 5 11
分析:
這裏有一個明顯的二元關係,就是i和j能否組成完全平方數。
每個柱子的球滿足這樣的特徵,相鄰的球能組成完全平方數,如果用弧表示,就是一個柱子代表一條連續的路徑,那麼這個問題就轉化爲最小路徑覆蓋了。
最小路徑覆蓋見我03最小路徑覆蓋的博文。
該題解法是枚舉答案x,然後求出最小路徑覆蓋看是否滿足小於等於n,如果滿足就增大x,同時增加相應的弧,再求。如果不滿足,則x-1爲答案。
下面建立二分圖,對於一個頂點X,分成拆兩個頂點出發點X‘和目標點X’‘。我設定了一個常量MAX,點X的出發點爲X,目標點爲MAX-X。
對於j>i,如果i和j能組成完全平方數,則添加弧<i,j>到圖中。還有要注意的是,因爲是順序放球,j是不能在i的下面的,所以不能加弧<j,i>。
另外這一題也可以二分答案,但是二分需要重新建圖,而枚舉,則只需要在之前的殘量網絡增加對應的弧,然後再增廣一次即可,所以還是用枚舉比較快。
代碼寫的很粗陋,這一次求二分圖用的是剛學的ISAP算法,另外多寫了一個逆向的BFS求路徑的過程,不寫也無所謂,時間相差不大。
學網絡流到現在,還是感覺最簡單的鄰接矩陣+BFS找增廣路徑的算法最好寫,可惜就是慢了點。啊啊啊啊啊,該死的鄰接表,DINIC和ISAP,還有沒學的那個什麼預流推進算法。坑爹 呢這是。
ISAP求最大流:
#include<cstdio>
#include<queue>
#include<vector>
#include<iostream>
#include<cstring>
using namespace std;
const int MAXN=4003;
const int MAX=4000;
struct edge{
int from,to,cap,flow;
edge(int a,int b,int c,int d):from(a),to(b),cap(c),flow(d)
{}
};
vector<edge> edges;
vector<int> g[MAXN];
int d[MAXN];
int p[MAXN];
int num[MAXN];
int visit[MAXN];
int cur[MAXN];
bool issquare[MAXN*MAXN>>2];
void add(int from,int to,int cap)
{
edges.push_back(edge(from,to,cap,0));
edges.push_back(edge(to,from,0,0));
int m=edges.size();
g[from].push_back(m-2);
g[to].push_back(m-1);
}
inline void build(int n)//對於球的數目從n-1到n增加相應的弧
{
for(int j=1;j<n;j++){
if(issquare[n+j])
add(j,MAX-n,1);
}
}
void BFS(int t,int s)//逆向BFS
{
queue<int> q;
d[t]=0;
visit[t]=true;
q.push(t);
while(!q.empty()){
int x=q.front();q.pop();
for(int i=0;i<g[x].size();i++){
edge & e=edges[g[x][i]^1];
if(d[e.from]==-1&&e.cap>e.flow){
d[e.from]=d[x]+1;
q.push(e.from);
}
}
}
}
int agument(int s,int t)//找到增廣路徑後,從匯點到源點找到最小可改進量然後逐個改進
{
int x=t,a=1<<30;
while(x!=s){
edge &e=edges[p[x]];
a=min(a,e.cap-e.flow);
x=e.from;
}
x=t;
while(x!=s){
edges[p[x]].flow+=a;
edges[p[x]^1].flow-=a;
x=edges[p[x]].from;
}
return a;
}
int maxflow(int &flow,int n,int s,int t)//isap算法,坑爹的一塌糊塗。
{
memset(d,-1,sizeof(d));
memset(cur,0,sizeof(cur));
build(n);
n<<=1;
BFS(t,s);
int x=s;
while(d[s]<n){
if(x==t){
flow+=agument(s,t);
x=s;
}
bool flag=false;
for(int i=cur[x];i<g[x].size();i++){
edge &e=edges[g[x][i]];
if(e.cap>e.flow&&d[x]==d[e.to]+1){
flag=true;
p[e.to]=g[x][i];
cur[x]=i;
x=e.to;
break;
}
}
if(!flag){
int m=n-1;
for(int i=0;i<g[x].size();i++){
edge &e=edges[g[x][i]];
if(e.cap>e.flow) m=min(m,d[e.to]+1);
}
if(--num[d[x]]==0) break;
d[x]=m+1;
num[d[x]]++;
cur[x]=0;
if(x!=s) x=edges[p[x]].from;
}
}
return flow;
}
void print(int i,int s,int t)//輸出路徑
{
visit[i]=true;
for(int j=0;j<g[i].size();j++)
{
edge&e=edges[g[i][j]];
if(!visit[e.to]&&e.to!=t&&e.flow==1){
printf(" %d",MAX-e.to);
print(MAX-e.to,s,t);
}
}
}
int main()
{
int n;
scanf("%d",&n);
for(int i=0;i<MAXN;i++)
g[i].clear();
edges.clear();
int s=0,t=MAXN-1;
for(int i=1;i<MAXN/2;i++)
issquare[i*i]=true;
for(int i=1;i<=MAX/2-1;i++)//從源點到所有出發點加弧,從所有目標點到匯點加弧
add(s,i,1),add(MAX-i,t,1);
int flow=0;
int x=1;
while(x-maxflow(flow,x,s,t)<=n)//求x個球的最小路徑覆蓋
x++;
printf("%d\n",x-1);
for(int i=1;i<x;i++)
if(!visit[i])
printf("%d",i),print(i,s,t),printf("\n");
return 0;
}