【codeforces】Codeforces Round #277.5 (Div. 2)

25 篇文章 0 订阅
21 篇文章 0 订阅

传送门:【codeforces】Codeforces Round #277.5 (Div. 2)


489A. SwapSort

选择排序!绝对的不超过n次交换。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
//#include <cmath>
using namespace std ;

typedef long long LL ;

#pragma comment ( linker , "/STACK:1024000000" )
#define rep( i , a , b ) for ( int i = ( a ) ; i <  ( b ) ; ++ i )
#define For( i , a , b ) for ( int i = ( a ) ; i <= ( b ) ; ++ i )
#define rev( i , a , b ) for ( int i = ( a ) ; i >= ( b ) ; -- i )
#define rec( i , A , o ) for ( int i = A[o] ; i != o ; i = A[i] )
#define clr( a , x ) memset ( a , x , sizeof a )

const int MAXN = 100005 ;
const int MAXE = 200005 ;
const int INF = 0x3f3f3f3f ;

struct Node {
	int x , y ;
	Node () {}
	Node ( int x , int y ) : x ( x ) , y ( y ) {}
} ;

Node ans[MAXN] ;
int a[MAXN] ;
int top ;
int n ;

void solve () {
	top = 0 ;
	rep ( i , 0 , n ) scanf ( "%d" , &a[i] ) ;
	rep ( i , 0 , n ) {
		int tmp = a[i] , pos = i ;
		rep ( j , i + 1 , n ) if ( a[j] < tmp ) {
			tmp = a[j] ;
			pos = j ;
		}
		if ( pos != i ) {
			ans[top ++] = Node ( i , pos ) ;
			a[pos] = a[i] ;
		}
	}
	printf ( "%d\n" , top ) ;
	rep ( i , 0 , top ) printf ( "%d %d\n" , ans[i].x , ans[i].y ) ;
}

int main () {
	while ( ~scanf ( "%d" , &n ) ) solve () ;
	return 0 ;
}

489B. BerSU Ball

我用的二分匹配,对于可以匹配的建边,然后跑最大匹配即可。

实际上可以排序贪心。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
//#include <cmath>
using namespace std ;

typedef long long LL ;

#pragma comment ( linker , "/STACK:1024000000" )
#define rep( i , a , b ) for ( int i = ( a ) ; i <  ( b ) ; ++ i )
#define For( i , a , b ) for ( int i = ( a ) ; i <= ( b ) ; ++ i )
#define rev( i , a , b ) for ( int i = ( a ) ; i >= ( b ) ; -- i )
#define rec( i , A , o ) for ( int i = A[o] ; i != o ; i = A[i] )
#define clr( a , x ) memset ( a , x , sizeof a )

const int MAXN = 100005 ;
const int MAXE = 200005 ;

struct Edge {
	int v , n ;
	Edge () {}
	Edge ( int v , int n ) : v ( v ) , n ( n ) {}
} ;

Edge E[MAXN] ;
int H[MAXN] , cntE ;
int link[MAXN] ;
int vis[MAXN] ;
int a[MAXN] ;
int b[MAXN] ;
int n , m ;

void clear () {
	cntE = 0 ;
	clr ( H , -1 ) ;
}

void addedge ( int u , int v ) {
	E[cntE] = Edge ( v , H[u] ) ;
	H[u] = cntE ++ ;
}

int find ( int u ) {
	for ( int i = H[u] ; ~i ; i = E[i].n ) if ( !vis[E[i].v] ) {
		int v = E[i].v ;
		vis[v] = 1 ;
		if ( link[v] == -1 || find ( link[v] ) ) {
			link[v] = u ;
			return 1 ;
		}
	}
	return 0 ;
}

int match () {
	clr ( link , -1 ) ;
	int ans = 0 ;
	For ( i , 1 , n ) {
		clr ( vis , 0 ) ;
		ans += find ( i ) ;
	}
	return ans ;
}

void solve () {
	clear () ;
	For ( i , 1 , n ) scanf ( "%d" , &a[i] ) ;
	scanf ( "%d" , &m ) ;
	For ( i , 1 , m ) scanf ( "%d" , &b[i] ) ;
	For ( i , 1 , n ) {
		For ( j , 1 , m ) if ( abs ( a[i] - b[j] ) <= 1 ) addedge ( i , j ) ;
	}
	printf ( "%d\n" , match () ) ;
}

int main () {
	while ( ~scanf ( "%d" , &n ) ) solve () ;
	return 0 ;
}

489C. Given Length and Sum of Digits...

易知最大的是从高位加起,最小的是从低位加起,注意1 0这样的情况。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
//#include <cmath>
using namespace std ;

typedef long long LL ;

#pragma comment ( linker , "/STACK:1024000000" )
#define rep( i , a , b ) for ( int i = ( a ) ; i <  ( b ) ; ++ i )
#define For( i , a , b ) for ( int i = ( a ) ; i <= ( b ) ; ++ i )
#define rev( i , a , b ) for ( int i = ( a ) ; i >= ( b ) ; -- i )
#define rec( i , A , o ) for ( int i = A[o] ; i != o ; i = A[i] )
#define clr( a , x ) memset ( a , x , sizeof a )

const int MAXN = 105 ;

int a[MAXN] ;
int b[MAXN] ;
int m , s ;

void solve () {
	if ( m > 1 && s < 1 || s > 9 * m ) {
		printf ( "-1 -1\n" ) ;
		return ;
	} else if ( m == 1 && s == 0 ) {
		printf ( "0 0\n" ) ;
		return ;
	}
	For ( i , 1 , m ) a[i] = b[i] = 0 ;
	int ss = s ;
	For ( i , 1 , m ) {//max
		if ( 9 - a[i] < ss ) {
			ss -= 9 - a[i] ;
			a[i] = 9 ;
		} else {
			a[i] = ss ;
			break ;
		}
	}
	ss = s ;
	rev ( i , m , 1 ) {//min
		if ( 9 - b[i] < ss ) {
			ss -= 9 - b[i] ;
			b[i] = 9 ;
		} else {
			b[i] = ss - 1 ;
			b[1] = b[1] + 1 ;
			break ;
		}
	}
	For ( i , 1 , m ) printf ( "%d" , b[i] ) ;
	printf ( " " ) ;
	For ( i , 1 , m ) printf ( "%d" , a[i] ) ;
	printf ( "\n" ) ;
}
	
int main () {
	while ( ~scanf ( "%d%d" , &m , &s ) ) solve () ;
	return 0 ;
}
489D. Unbearable Controversy of Being

以每个点为起点bfs一次,找到起点到每个距离起点2的点的路径数x,ans+=x*(x-1)/2。易知最多每次只会走m条边,所以复杂度为o(nm)。

PS:实际上根本不需要bfs,用邻接表判断就好了。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
//#include <cmath>
using namespace std ;

typedef long long LL ;

#pragma comment ( linker , "/STACK:1024000000" )
#define rep( i , a , b ) for ( int i = ( a ) ; i <  ( b ) ; ++ i )
#define For( i , a , b ) for ( int i = ( a ) ; i <= ( b ) ; ++ i )
#define rev( i , a , b ) for ( int i = ( a ) ; i >= ( b ) ; -- i )
#define rec( i , A , o ) for ( int i = A[o] ; i != o ; i = A[i] )
#define clr( a , x ) memset ( a , x , sizeof a )

const int MAXN = 3003 ;
const int MAXE = 30005 ;

struct Edge {
    int v , n ;
    Edge () {}
    Edge ( int v , int n ) : v ( v ) , n ( n ) {}
} ;

struct Node {
    int x , d ;
    Node () {}
    Node ( int x , int d ) : x ( x ) , d ( d ) {}
} ;

Node Q[MAXE] ;
Edge E[MAXE] ;
int head , tail ;
int H[MAXN] , cntE ;
LL G[MAXN][MAXN] ;
int n , m ;

void clear () {
    cntE = 0 ;
    clr ( H , -1 ) ;
}

void addedge ( int u , int v ) {
    E[cntE] = Edge ( v , H[u] ) ;
    H[u] = cntE ++ ;
}

void bfs ( int root ) {
    head = tail = 0 ;
    Q[tail ++] = Node ( root , 0 ) ;
    while ( head != tail ) {
        Node o = Q[head ++] ;
        if ( head == MAXE ) head = 0 ;
        int u = o.x ;
        int d = o.d ;
        if ( d == 2 ) {
            G[root][u] ++ ;
            continue ;
        }
        for ( int i = H[u] ; ~i ; i = E[i].n ) {
            int v = E[i].v ;
            if ( v == root ) continue ;
            Q[tail ++] = Node ( v , d + 1 ) ;
        }
    }
}

void solve () {
    int u , v ;
    clear () ;
    clr ( G , 0 ) ;
    rep ( i , 0 , m ) {
        scanf ( "%d%d" , &u , &v ) ;
        addedge ( u , v ) ;
    }
    For ( i , 1 , n ) bfs ( i ) ;
    LL ans = 0 ;
    For ( i , 1 , n ) For ( j , 1 , n ) ans += G[i][j] * ( G[i][j] - 1 ) / 2 ;
    printf ( "%I64d\n" , ans ) ;
}

int main () {
    while ( ~scanf ( "%d%d" , &n , &m ) ) solve () ;
    return 0 ;
}

489E. Hiking

题意:一个旅行者想从河的起点(坐标0)走到河的终点(编号n)途中有n个休息站(包括终点),休息站有2个属性:(1)坐标xi,(2)风景价值vi。每次只能从一个休息站走到另一个休息站,中间不能停留,旅行者想每次走L距离,但是这基本呢不可能,于是给了这样的一个定义:每次走ri的距离(即选择的两休息站之间的距离),则他会感到失意,失意指数为sqrt ( | ri - L | )。现在你的任务是选出一些休息站,使得(失意指数之和/休息站的风景价值之和)最小。起点为0,终点只能为n。休息站的坐标按照输入顺序严格递增给出。

分析:

考虑设r = sum { sqrt ( | ri - L | ) } / sum { vi }。

转化一下就成了sum { sqrt ( | ri - L | ) } - r * sum { vi } = 0。

令G(r)= sum { sqrt ( | ri - L | ) } - r * sum { vi }

我们的目标是最小化G(r),最小化G(r)等同于最小化r。

如果G(r)< 0,说明r取大了,但是sum { sqrt ( | ri - L | ) } / sum { vi } <= r,r可以更优!

如果G(r)= 0,此时r恰是最优值。

如果G(r)> 0,说明r取小了,sum { sqrt ( | ri - L | ) } / sum { vi }不可能比r小。

如果G(r)是一个单调递减函数,那么我们便可以用二分的方法去求得最优的r了,

当G(r)< 0时调整上界,

当G(r)>0时调整下界,

当二分上下界r-l<eps时大致认为此时的r即最优解。

G(r)函数的最小值求解可用O(n^2)的dp求解。

dp[i] = min { dp[j] + sqrt ( | x[i] - x[j] - L | ) - r * a[i] }。

幸运的是,G(r)的确是一个单调递减函数,所以二分是可行的。

关于本题算法正确性的证明,应该去看一下分数规划问题,本题就是一个分数规划问题,然后用dp求G(r)函数。


#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std ;

typedef long long LL ;

#pragma comment ( linker , "/STACK:1024000000" )
#define rep( i , a , b ) for ( int i = ( a ) ; i <  ( b ) ; ++ i )
#define For( i , a , b ) for ( int i = ( a ) ; i <= ( b ) ; ++ i )
#define rev( i , a , b ) for ( int i = ( a ) ; i >= ( b ) ; -- i )
#define rec( i , A , o ) for ( int i = A[o] ; i != o ; i = A[i] )
#define clr( a , x ) memset ( a , x , sizeof a )

const int MAXN = 1005 ;
const double eps = 1e-8 ;
const double INF = 1e30 ;

double dp[MAXN] ;
int x[MAXN] , b[MAXN] ;
double a[MAXN] ;
int pre[MAXN] ;
int S[MAXN] , top ;
int n , m ;

void solve () {
	a[0] = x[0] = b[0] = 0 ;
	For ( i , 1 , n ) scanf ( "%d%d" , &x[i] , &b[i] ) ;
	double l = 0 , r = 1e10 ;
	while ( l < r - eps ) {
		double mid = ( l + r ) / 2 ;
		For ( i , 1 , n ) a[i] = mid * b[i] ;
		dp[0] = 0 ;
		For ( i , 1 , n ) {
			dp[i] = INF ;
			rev ( j , i - 1 , 0 ) {
				double tmp = dp[j] + sqrt ( double ( abs ( x[i] - x[j] - m ) ) ) - a[i] ;
				if ( dp[i] > tmp ) dp[i] = tmp , pre[i] = j ;
			}
		}
		if ( dp[n] > -eps ) l = mid ;
		else r = mid ;
	}
	top = 0 ;
	while ( n ) {
		S[top ++] = n ;
		n = pre[n] ;
	}
	rev ( i , top - 1 , 0 ) printf ( "%d%c" , S[i] , i ? ' ' : '\n' ) ;
}

int main () {
	while ( ~scanf ( "%d%d" , &n , &m ) ) solve () ;
	return 0 ;
}

489F. Special Matrices

我是O(n^3)暴力的。。不要嘲笑我T  T

首先可以预处理出每一列还剩多少1可以用,用a[i]表示。

dp[i][j][k]表示已经处理到第i列,已经有j行放置了两个1,同时有k行已经放置了一个1。

当a[i] = 0时:

dp[i][j][k] = dp[i - 1][j][k]

当a[i] = 1时:

dp[i][j][k + 1] += ( n - m - j - k ) * dp[i - 1][j][k](增加一个1行)

    当k > 0:

    dp[i][j + 1][k - 1] += k * dp[i - 1][j][k](将一个1行变为2行)

当a[i] = 2时:

dp[i][j][k + 2] += ( n - m - j - k ) * ( n - m - j - k - 1 ) / 2 * dp[i - 1][j][k](增加两个1行)

    当k > 0:

    dp[i][j + 1][k] += ( n - m - j - k ) * k * dp[i - 1][j][k](将一个1行变为2行,同时再增加一个1行)

    当k > 1:

    dp[i][j +2][k - 2] += k * ( k - 1 ) / 2 * dp[i - 1][j][k](将两个1行变为2行)


这就是O(n^3)的算法,稍微优化一下,156ms。。。


正确的姿势应该是O(n^2)的算法,等我有空去想想。


#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
//#include <cmath>
using namespace std ;

typedef long long LL ;

#pragma comment ( linker , "/STACK:1024000000" )
#define rep( i , a , b ) for ( int i = ( a ) ; i <  ( b ) ; ++ i )
#define For( i , a , b ) for ( int i = ( a ) ; i <= ( b ) ; ++ i )
#define rev( i , a , b ) for ( int i = ( a ) ; i >= ( b ) ; -- i )
#define rec( i , A , o ) for ( int i = A[o] ; i != o ; i = A[i] )
#define clr( a , x ) memset ( a , x , sizeof a )

const int MAXN = 505 ;

int mod ;
int dp[2][MAXN][MAXN] ;
int a[MAXN] ;
int n , m ;

inline void add ( int& x , int y ) {
	x += y ;
	if ( x >= mod ) x -= mod ;
}

void solve () {
	int x , cur = 0 ;
	clr ( dp , 0 ) ;
	For ( i , 1 , n ) a[i] = 2 ;
	For ( i , 1 , m ) For ( j , 1 , n ) {
		scanf ( "%1d" , &x ) ;
		a[j] -= x ;
	}
	dp[0][0][0] = 1 ;
	int s = n - m ;
	For ( i , 1 , n ) {
		cur ^= 1 ;
		clr ( dp[cur] , 0 ) ;
		For ( j , 0 , s ) {
			if ( j > i * 2 ) break ;
			For ( k , 0 , s - j ) if ( dp[cur ^ 1][j][k] ) {
				if ( a[i] == 0 ) add ( dp[cur][j][k] , dp[cur ^ 1][j][k] ) ;
				else if ( a[i] == 1 ) {
					if ( k > 0 ) add ( dp[cur][j + 1][k - 1] , ( LL ) k * dp[cur ^ 1][j][k] % mod ) ;
					add ( dp[cur][j][k + 1] , ( LL ) ( s - k - j ) * dp[cur ^ 1][j][k] % mod ) ;
				} else {
					if ( k > 1 ) add ( dp[cur][j + 2][k - 2] , ( LL ) k * ( k - 1 ) / 2 * dp[cur ^ 1][j][k] % mod ) ;
					if ( k > 0 ) add ( dp[cur][j + 1][k] , ( LL ) ( s - k - j ) * k * dp[cur ^ 1][j][k] % mod ) ;
					add ( dp[cur][j][k + 2] , ( LL ) ( s - k - j ) * ( s - k - j - 1 ) / 2 * dp[cur ^ 1][j][k] % mod ) ;
				}
			}
		}
	}
	printf ( "%d\n" , dp[cur][n - m][0] ) ;
}

int main () {
	while ( ~scanf ( "%d%d%d" , &n , &m , &mod ) ) solve () ;
	return 0 ;
}


-----------------------update-----------------------

F题O(n^2)解法。

设dp[i][j]表示已经有i列上有两个1,j列上有1个1,剩下的n-i-j列上还没有1。

那么,由于i的不递减,所以我们可以将这个作为递推的第一维。

dp[i][j + 2] += ( n - i - j ) * ( n - i - j - 1 ) / 2 * dp[i][j].(选择没有1的两列将其变成有一个1的两列)

dp[i + 1][j] += j * ( n - i - j ) * dp[i][j].(j>0)(选择一列有一个1的变成有两个1的,同时选择没有1的变成有一个1的)

dp[i + 2][j - 2] += j * ( j - 1 ) / 2 * dp[i][j].(j>1)(选择有一个1的两列变成有两个1的两列)

这样推法的可行性在于充分利用1的数量是固定的隐性条件,到最后终点dp[n][0]时的一定是合法的。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
//#include <cmath>
using namespace std ;

typedef long long LL ;

#pragma comment ( linker , "/STACK:1024000000" )
#define rep( i , a , b ) for ( int i = ( a ) ; i <  ( b ) ; ++ i )
#define For( i , a , b ) for ( int i = ( a ) ; i <= ( b ) ; ++ i )
#define rev( i , a , b ) for ( int i = ( a ) ; i >= ( b ) ; -- i )
#define rec( i , A , o ) for ( int i = A[o] ; i != o ; i = A[i] )
#define clr( a , x ) memset ( a , x , sizeof a )

const int MAXN = 505 ;

int mod ;
int dp[MAXN][MAXN] ;
int a[MAXN] ;
int n , m ;

inline void add ( int& x , int y ) {
	x += y ;
	if ( x >= mod ) x -= mod ;
}

void solve () {
	int v , cur = 0 ;
	clr ( dp , 0 ) ;
	clr ( a , 0 ) ;
	For ( i , 1 , m ) {
		getchar () ;
		For ( j , 1 , n ) a[j] += getchar () - '0' ;
	}
	int x = 0 , y = 0 ;
	For ( i , 1 , n ) {
		if ( a[i] == 2 ) ++ x ;
		else if ( a[i] == 1 ) ++ y ;
	}
	dp[x][y] = 1 ;
	For ( i , x , n ) {
		For ( j , 0 , n - i ) if ( dp[i][j] ) {
			add ( dp[i][j + 2] , ( LL ) ( n - i - j ) * ( n - i - j - 1 ) / 2 * dp[i][j] % mod ) ;
			if ( j > 0 ) add ( dp[i + 1][j] , ( LL ) j * ( n - i - j ) * dp[i][j] % mod ) ;
			if ( j > 1 ) add ( dp[i + 2][j - 2] , ( LL ) j * ( j - 1 ) / 2 * dp[i][j] % mod ) ;
		}
	}
	printf ( "%d\n" , dp[n][0] ) ;
}

int main () {
	while ( ~scanf ( "%d%d%d" , &n , &m , &mod ) ) solve () ;
	return 0 ;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值