题目链接
https://www.luogu.org/problemnew/solution/P2765
魔术球问题
这道题有很多奇奇怪怪的做法,比如打表,贪心,这些方法本文不进行讲解,本文讲解的是用最大流(二分图匹配)来解决。
讲讲博主的思考历程,如果把这个图建出来,编号小的向编号大的连边,条件是他们的和为平方数,那么得到一个DAG,每一根柱子相当于是一条图上的路径,每条路径之间不能有交集但是又要包含所有点,于是想到了最小路径覆盖,然后最小路径覆盖=顶点数-二分图最大匹配数,把模型简化之后,就是每次新加进来一个点,把他向所有比他小且与他的和为平方数的点连边,称这些点为关键点,如果一个关键点没有匹配,说明他位于一根柱子的顶端,那么我们把该关键点与新加进来这个点匹配就行了,如果一个关键点没匹配,那么他肯定是不位于顶端了。
最后输出方案的时候从前往后一直找匹配就行了。
博主的语文水平有限,如果看不懂的话可以进行代码理解。
#include<cstdio>
#include<algorithm>
#include<cctype>
#include<cstring>
#include<iostream>
#include<cmath>
#define LL long long
#define INF (2139062143)
#define N (100001)
using namespace std;
int n,now,k,ans;
int com[N],a[N],nxt[N],head[N];
bool vis[N];
template <typename T> void read(T&t) {
t=0;
bool fl=true;
char p=getchar();
while (!isdigit(p)) {
if (p=='-') fl=false;
p=getchar();
}
do {
(t*=10)+=p-48;p=getchar();
}while (isdigit(p));
if (!fl) t=-t;
}
inline void add(int x,int y){
a[++k]=y,nxt[k]=head[x],head[x]=k;
}
bool DFS(int x){
for (int p=head[x];p;p=nxt[p]){
if (!vis[a[p]]){
vis[a[p]]=1;
if (!com[a[p]]||DFS(com[a[p]])){
com[a[p]]=x;
return 1;
}
}
}
return 0;
}
int main(){
read(n);
while (1){
now++;
for (int j=1;j<now;j++){
int kk=sqrt(now+j);
if (kk*kk==now+j){
add(now,j);
}
}
memset(vis,0,sizeof(vis));
ans=ans+1-DFS(now);
if (ans>n) break;
}
now--;
printf("%d\n",now);
memset(vis,0,sizeof(vis));
for (int i=1;i<=now;i++){
if (!vis[i]){
vis[i]=1;
int her=i;
printf("%d ",her);
while (com[her]){
her=com[her];
printf("%d ",her);
vis[her]=1;
}
puts("");
}
}
return 0;
}
这里求二分图最大匹配用的是匈牙利算法,就是代码中的DFS.