传送门:【codeforces】Codeforces Round #277.5 (Div. 2)
选择排序!绝对的不超过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 ;
}
我是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 ;
}