前言
具体的数学关系还是不会证,除了能打表发现球数规律外,内在原理也弄不清楚。
题面
sol
很难想象是一道网络流。第一感是数学。
但是看到数据范围比较小,估计可以暴力dp。
又发现,状态转移比较难,状态调整比较多,dp估计难设,又看到数据范围又小于dp通常可做范围,所以用同样以规划和状态设计为关键字的,状态调整能力更强的,复杂度可能略高的网络流来做。
实际上,这种题考点不在网络流的精深图论,而在于网络流的规划,计算,调整能力,也就是通常说的的建模能力。只有最后输出方案需要一点图论知识。
多啰嗦一句的是,网络流,建状态同样也有优化,尽量少的边与点,边权尽量更加集中(少建INF边)会优化速度.
然后这道题首先,每个点只能选一次,这就可以用一个套路,抱一个点拆成一条边权为1的边,那么该边就只能经过一次了。
然后算贡献的话,就通过原始dp来考虑。比如,写dp就会直接转移到一个与自己加起来为平方和的点,那么网络流也是可以流向一条与自己加起来为平方和的点,然后流入成功就算一次贡献。网络流算贡献就是向汇点T流入1的流量。
显然dp还是可以自己创一组,就是流向一个柱子,注意到柱子也只能被流一次,所以就用上述的套路,跟流向一个点是一样的。
然后源点肯定是相当于去激活每一个点,那么每一个点都可以被S激活,所以搞一个S流向每一个点的流量是1的边。
有人说跟最小点覆盖很像,其实就是用到了锁定一个点只能流一次这个套路。
然后注意到,这个题说了只能选满1-m个球,根据dp的套路就是一个一个考虑,然后大的只能放向小的,所以就从小的往大枚举的放就可以了,如果某一个小的放不进当前的状态,那么就break。
网络流还是胜在自己的调整能力。单用点与边来规划处所有的情况,模拟水流,就会有着较强的调整能力,这就使得他只需用当前这张图来表示当前的状态,而不用像dp那样存储下每一种状态。dp的调整能力也不强,当一种情况的加入会大量改变已有状态时,dp就会显的很麻烦,而网络流则会自动的调整了当前的状态。目前的网络流只能通过构建边与点的关系来构造,所有的套路很少,也只限于这一个点与边的圈子,而dp却显得更加丰富与综合。
code
#include<bits/stdc++.h>
using namespace std;
inline char gc(){
static char buf[1<<7],*p1=buf,*p2=buf;
return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,1<<7,stdin),p1==p2)?EOF:*p1++;
}
template <class T>
inline void read(T&data){
data=0;
register char ch=0;
while(ch<'0'||ch>'9')ch=gc();
while(ch<='9'&&ch>='0'){
data=(data<<3)+(data<<1)+(ch^48);
ch=gc();
}
return;
}
template <class R>
inline void write(R data){
if(data>9)write(data/10);
putchar(data%10+'0');
}
const int _ = 200001,__ = 4000;
int n,to[_<<1],head[_],cur[_],S,T,nxt[_<<1],w[_<<1],cnt=-1,vnt,ball1[_],ball2[_],reball[_];
bool vis[_];
inline void add(register int a,register int b,register int c ){
to[++cnt]=b,nxt[cnt]=head[a],head[a]=cnt,w[cnt]=c;
to[++cnt]=a,nxt[cnt]=head[b],head[b]=cnt,w[cnt]=0;
}
int dfs(register int now,register int flow){
if(now==T)return flow;
vis[now]=1;
for(register int i=head[now];~i;i=nxt[i]){
if(w[i]==0)continue;
if(vis[to[i]])continue;
register int di=dfs(to[i],min(flow,w[i]));
if(di>0){
w[i]-=di,w[i^1]+=di;return di;
}
}
return 0;
}
int main(){
memset(head,-1,sizeof(head));
read(n);//柱子的标号就是1到n了
S=n+1,T=S+1;//这两个就这样定了吧注意球从什么开始标号
vnt=T+1;
for(register int i=1;i<=n;++i)
add(i,T,1);
register int ret=0,ans=0;
for(register int i=1;1;++i){
ball1[i]=++vnt,ball2[i]=++vnt;
reball[vnt-1]=i;
add(ball2[i],T,1);
add(S,ball1[i],1);
for(register int j=1;j<=n;++j)add(ball1[i],j,1);
if(i!=1){
for(register int j=1;j*j<(i<<1);++j){
if(j*j<=i)continue;
add(ball1[i],ball2[j*j-i],1);
}
}
ret=dfs(S,1);
memset(vis,0,sizeof(vis));
if(ret==0){
ans=i-1;
break;
}
}
write(ans);puts("");
for(register int i=1;i<=ans;++i){
if(vis[i])continue;
register int now = i;
do{
write(now);putchar(' ');vis[now]=1;
for(register int j=head[ball2[now]];~j;j=nxt[j]){
if(to[j]==T)continue;
if(w[j^1]==0){
now=reball[to[j]];break;
}
}
}while(!vis[now]);
puts("");
}
}