P2765 魔术球问题
每个数可以放在和它和是平方的数上面
我们可以想到,如果一个点可以放在某个点上面是那个点向它连边
如果一个点向它所有能连的点连边,那么它的最小链覆盖就是我们要找的答案
经过简单推理可以发现,每个点可以连接的点的大小是递增的,那么把编号小的点放在柱子下面一定更优
这样连边就行了
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<vector>
#include<cctype>
#include<cstring>
using namespace std;
#define rg register
inline int read(){
rg char ch=getchar();
rg int x=0,f=0;
while(!isdigit(ch)) f|=(ch=='-'),ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return f?-x:x;
}
int n;
const int N=5005,M=12005<<2;
int head[N],ver[M],nxt[M],flow[M],tot=1;
int dis[N],cur[N];
inline void add(int x,int y,int z){
ver[++tot]=y;
flow[tot]=z;
nxt[tot]=head[x];
head[x]=tot;
}
inline void adds(int x,int y,int z){
add(x,y,z);
add(y,x,0);
}
int s,t;
inline int bfs(){
queue<int> q;
q.push(s);
memset(dis,0,sizeof dis);
dis[s]=1;
while(!q.empty()){
int x=q.front();q.pop();
cur[x]=head[x];
for(int y,i=head[x];i;i=nxt[i]){
y=ver[i];
if(!dis[y]&&flow[i]){
dis[y]=dis[x]+1;
q.push(y);
}
}
}
return dis[t];
}
int dfs(int x,int f){
if(x==t) return f;
int used=0;
for(int y,w,&i=cur[x];i;i=nxt[i]){
y=ver[i];
if(dis[y]==dis[x]+1&&flow[i]){
w=dfs(y,min(f-used,flow[i]));
if(w){
flow[i]-=w;
flow[i^1]+=w;
used+=w;
if(used==f) return f;
}
}
}
if(!used) dis[x]=0;
return used;
}
inline int dinic(){
int ans=0;
while(bfs()) ans+=dfs(s,0x3f3f3f3f);
return ans;
}
int vis[N];
int base=2500;
void work(int x){
vis[x]=1;
printf("%d ",x);
for(int i=head[x];i;i=nxt[i]){
if(i&1||flow[i]) continue;
work(ver[i]-base);
}
}
signed main(){
n=read();
int now=0;
s=0;t=5004;
adds(s,1,1);
adds(1+base,t,1);
for(int i=2;;++i){
adds(s,i,1);
adds(i+base,t,1);
for(int j=1;j*j<i+i;++j){
if(j*j<=i) continue;
adds(j*j-i,i+base,1);
//cout<<j*j-i<<" link "<<i<<endl;
}
now+=dinic();
//cout<<i<<" "<<now<<endl;
if(i-now<=n) continue;
printf("%d\n",i-1);
for(int j=1;j<i;++j) if(!vis[j]) work(j),puts("");
break;
}
return 0;
}