P2765 魔术球问题&&二分图
题意
思路
1
根据此题输入m的范围,可以知道此题的答案上限约为5000
考虑逆向二分求解(实际上可以直接枚举)
2
此题可以抽象成在图上求最少链的个数
我们把所有数向比他大的、与他的和为平方数的数建边
可以看出是二分图最大匹配问题
结合图更清晰:
此时图上最少链的个数为
\(n\)(点数)\(- ans\)(最大匹配数)即\(6-3=3\)
这三条链分别为:
1 3 6
4 5
2
可以看作从 \(1\) 开始,不断在从左边跳到右边
- 左边的 1 连接了右边的 3,这条链上有\(1 3\)。3已经被加到一条链中,标记一下,再遍历到 3 时直接跳过
左边的 3 又连接到了右边的 6,这条链就变成了\(1 3 6\) 标记 6 - 左边的 2 没有连接右边的点,这条链上只有 \(2\)
- 遍历到 3,3 已经被标记,continue
- 4 连接了 5,标记 5,此时这条链变为 \(45\)
- 5 被标记,continue
- 6 被标记,结束。
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=5005;
int m;
vector<int>g[maxn*2];
int result[maxn*2];
int mm[maxn*2];
bool vis[maxn*2];
bool dfs(int p)
{
for(int j=0;j<g[p].size();j++)
{
if(!vis[g[p][j]]){
vis[g[p][j]]=true;
if(result[g[p][j]]==0||dfs(result[g[p][j]]))
{
result[g[p][j]]=p;
mm[p]=g[p][j];
return true;
}
}
}
return false;
}
bool is_sqre(int p)
{
int t = sqrt(p);
if(t * t == p){
return true;
}
return false;
}
bool check(int n)
{
memset(mm,0,sizeof(mm));
int ans=0;
for(int i=1;i<=n;i++)
g[i].clear();
memset(result,0,sizeof(result));
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
if(is_sqre(i+j)) g[i].push_back(j);
}
}
for(int i=1;i<=n;i++)
{
if(dfs(i)) ans++;
memset(vis,false,sizeof(vis));
}
if(n-ans>m) return false;//需要更多的柱子
else return true;
}
int main()
{
cin>>m;
int l=1,r=5000;
while(l<r){
int mid=(l+r)>>1;
if(check(mid)) l=mid+1;
else r=mid;
}
cout<<l-1<<endl;
for(int j=1;j<=l-1;j++)
{
if(!vis[j])
{
for(int i=j;i;i=mm[i])
{
cout<<i<<" ";
vis[i]=true;
}
cout<<endl;
}
}
return 0;
}