洛谷P2765 魔术球问题 网络流之最小路径覆盖 || 贪心

版权声明:本文为博主原创文章,未经博主允许也可以转载。 https://blog.csdn.net/FrankAx/article/details/83215215

P2756P2756

思路:k根柱子相当于k条路径覆盖,求最多能放n个球的n的大小。
根据每个球相邻球必须相加为平方数可以建边,然后由:
最小路径覆盖 = n - 最大二分匹配,
可以枚举n,也可以二分查找(因为球数n随柱子k的增大是单调不递减的)。
这样可以得到球数n的大小。
然后输出每条简单路,和求最小路径覆盖一样,每次增广成功记录连接的点就行了。
Code:

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
const int AX = 1e4 + 666 ;
int n ; 
int cur[AX] ;
int head[AX] ;
int d[AX] ;
int nxt[AX];
int s = 0 , t = 6666 ;
struct Node{
	int u , v , flow , next1 ; 
	Node( int u = 0 , int v = 0 , int flow = 0 , int next1 = 0 ):u(u),v(v),flow(flow),next1(next1){}
}G[AX*10];
int tot ; 
void addEdge( int u , int v , int w ){
	G[tot] = Node( u , v , w , head[u] ) ; head[u] = tot++ ;
	G[tot] = Node( v , u , 0 , head[v] ) ; head[v] = tot++ ;
}

bool bfs(){
	memset( d , -1 , sizeof(d) ) ;
	d[s] = 0 ;
	queue<int>q;
	q.push(s);
	while( !q.empty() ) {
		int u = q.front() ; 
		q.pop() ; 
		for( int i = head[u] ; ~i ; i = G[i].next1 ) {
			int v = G[i].v ; 
			if( d[v] == -1 && G[i].flow ){
				d[v] = d[u] + 1 ; 
				q.push(v) ;
				if( v == t ) return true;
			}
		}
	}
	return ~d[t] ; 
}

int dfs( int u , int cap ){
	if( u == t || !cap ) return cap ; 
	int r = 0 ;
	for( int i = cur[u] ; ~i ; i = G[i].next1 ){
		int v = G[i].v ;
		if( G[i].flow && d[v] == d[u] + 1 ){
			int tmp = min( G[i].flow , cap - r ) ;
			cur[u] = i ; 
			tmp = dfs( v , tmp );
			r += tmp ;
			if( tmp ){
				nxt[u] = v ; 
			}
			G[i].flow -= tmp ; 
			G[i^1].flow += tmp ; 
			if( r == cap ) break ;
		}
	}
	if( !r ) d[u] -= 2 ; 
	return r ; 
}

int Dinic(){
	int cnt = 0 ;
	int tmp ; 
	while( bfs() ){
		memcpy( cur , head , sizeof(head) ) ;
		while( tmp = dfs( s , INF ) ) cnt += tmp ; 
	}
	return cnt ;
}

int k ;
bool solve( int n ) {
	tot = 0 ;	
	memset( nxt , -1 , sizeof(nxt) );
	memset( head , -1 , sizeof(head) ) ; 
	for( int i = 1 ; i <= n ; i++ ){
		for( int j = i + 1 ; j <= n ; j++ ){
			int x = sqrt(i + j) ;
			if( x * x == ( i + j ) ) { 
				addEdge( i , n + j , 1 ) ;
			}
		}
	}
	for( int i = 1 ; i <= n ; i++ ){
		addEdge( s , i , 1 ) ;
		addEdge( i + n , t , 1 ) ;
	}
	int flow = Dinic() ;
	n -= flow ; 
	return ( k < n ) ;
}

int main(){
	scanf("%d",&k) ; 
	int l = 0 , r = 1600 ; 
	while( l < r ){
		int mid = ( l + r ) / 2 ;
		if( solve(mid) ){
			r = mid ; 
		}else l = mid + 1 ;
	}
	n = l - 1 ;
	solve( n ) ;
	printf("%d\n",n);
	for( int i = 1 ; i <= n ; i++ ){
		if( nxt[i] == -1 ) continue ; 
		int u = i ;
		while( ~u ){
			if( u >= n ) u -= n ;
			printf("%d ",u);
			int x = nxt[u];
			nxt[u] = -1;
			u = x ;
		}
		printf("\n");
	}
	return 0 ; 
}

贪心做法:
先预处理平方数,然后就枚举球能放就放,不能放就加柱子,直到柱子数等于输入的柱子数。

#include <bits/stdc++.h>
using namespace std;
const int AX = 4e6 + 666;
bool isSqu[AX];
int vis[60][1600] ;
int a[60];
void init(){
	int s = 0 ;
	for( int i = 1 ; i <= 2000 ; i += 2 ) { isSqu[s+i] = true;  s += i;}
}
int main(){
	int k ;
	scanf("%d",&k);
	int n ;
	init();
	int cur = 1 ;
	a[1] = 1 ;
	memset( vis , false , sizeof(vis) ) ; 
	vis[1][1] = 1; 
	for( n = 2 ; ; n ++ ){
		for( int i = 1 ; i <= cur ; i++ ){
			if( isSqu[n+a[i]] ){
				a[i] = n ;
				vis[i][n] = true ;   
				goto f;
			}
		}
		if( k == cur ) break;
		a[++cur] = n ; 
		vis[cur][n] = true ; 
		f:;
	}
	printf("%d\n",n-1);
	for( int i = 1 ; i <= k ; i++ ){
		for( int j = 1 ; j < n ; j++ ){
			if( vis[i][j] ){
				printf("%d ",j);
			}
		}printf("\n");
	}
	return 0 ; 
}

猜你喜欢

转载自blog.csdn.net/FrankAx/article/details/83215215