传送门:【ACdream】Andrew Stankevich's Contest (3)
B:1227 Beloved Sons
这题费用流妥妥的超时,但是可以KM匹配做,用KM匹配的话就是很裸的最大权匹配了,也就不多说了。
我们队比赛的时候费用流TLE,KM边权以为不用开平方(反例:6、6和1、10),然后又用了稳定婚姻。。。无所不用其极。。
最后是用了贪心+二分匹配做的。
思路:
我们易知:如果一个人在之前的匹配中属于一条匹配边,则之后一定一直在匹配中(寻找增广路不过是将一个人从一条匹配边转移到另一条匹配边而已)。又因为如果二分匹配中的一个结点在以它作为起点寻找增广路时失败,则以后也不会找到它的匹配边(否则现在就可以找到)。
因此我们得到了这样一个算法:以男孩为左边的点,女孩为右边的点,按照每个节点(男孩)的重要性降序排序,依次选择每个点(男孩)去寻找增广路,最后二分匹配以后每个左边的点所属的匹配边对应的右边的点即准备与之姻配的女孩(当然,不在匹配边中的男孩注孤生~)。这样得到的点集一定是最优(之一)的。
#include <cstdio> #include <cstring> #include <algorithm> //#include <cmath> #include <queue> using namespace std ; typedef long long LL ; #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 travel( e , H , u ) for ( Edge* e = H[u] ; e ; e = e -> next ) #define clr( a , x ) memset ( a , x , sizeof a ) #define cpy( a , x ) memcpy ( a , x , sizeof a ) const int MAXN = 405 ; const int MAXE = 1000000 ; struct Edge { int v ; Edge* next ; } E[MAXE] , *H[MAXN] , *edge ; struct Node { int w , idx ; bool operator < ( const Node& a ) const { return w > a.w ; } } node[MAXN] ; int ans[MAXN] ; int Lx[MAXN] ; int Ly[MAXN] ; int vis[MAXN] ; int idx[MAXN] ; int Time ; int n ; void clear () { edge = E ; clr ( H , 0 ) ; } void addedge ( int u , int v ) { edge->v = v ; edge->next = H[u] ; H[u] = edge ++ ; } int find ( int u ) { travel ( e , H , u ) { int v = e->v ; if ( vis[v] != Time ) { vis[v] = Time ; if ( Ly[v] == -1 || find ( Ly[v] ) ) { Lx[u] = v ; Ly[v] = u ; return 1 ; } } } return 0 ; } void match () { clr ( Lx , -1 ) ; clr ( Ly , -1 ) ; For ( i , 1 , n ) { ++ Time ; find ( i ) ; } } void solve () { int m , x ; clear () ; For ( i , 1 , n ) scanf ( "%d" , &node[i].w ) , node[i].idx = i ; sort ( node + 1 , node + n + 1 ) ; For ( i , 1 , n ) idx[node[i].idx] = i ; For ( i , 1 , n ) { scanf ( "%d" , &m ) ; For ( j , 1 , m ) { scanf ( "%d" , &x ) ; addedge ( idx[i] , x ) ; } } match () ; For ( i , 1 , n ) ans[node[i].idx] = ( Lx[i] != -1 ? Lx[i] : 0 ) ; For ( i , 1 , n ) printf ( "%d%c" , ans[i] , i < n ? ' ' : '\n' ) ; } int main () { clr ( vis , 0 ) ; Time = 0 ; while ( ~scanf ( "%d" , &n ) ) solve () ; return 0 ; }
E:1230 Strong Defence
思路很简单:求一遍最短路,在不超过到终点的最短路长度的所有长度集合里,每个长度都是一个割集!(证明没想好,不过觉得挺显然的。。)
那么接下来的过程就简单了,把所有边归属与其距离起点较远的点(可能出现两端距离起点长度相同的点,这个就随便归属了,肯定不是在割集里的边,反正不能少就行了),当然距离起点的距离超过到终点的最短路长度的边直接忽略。
最后就是愉快的输出环节了。
#include <cstdio> #include <cstring> #include <algorithm> //#include <cmath> #include <queue> using namespace std ; typedef long long LL ; #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 travel( e , H , u ) for ( Edge* e = H[u] ; e ; e = e -> next ) #define clr( a , x ) memset ( a , x , sizeof a ) #define cpy( a , x ) memcpy ( a , x , sizeof a ) const int MAXN = 1005 ; const int MAXQ = 2000000 ; const int MAXE = 2000000 ; const int INF = 0x3f3f3f3f ; struct Edge { int v , idx ; Edge* next ; } E[MAXE] , *H[MAXN] , *edge , *D[MAXN] ; struct Seg { int u , v ; } seg[MAXE] ; int d[MAXN] ; int vis[MAXN] ; int num[MAXN] ; int head , tail ; int Q[MAXQ] ; int n , m , s , t ; void clear () { edge = E ; clr ( H , 0 ) ; clr ( D , 0 ) ; } void addedge ( int u , int v , int idx ) { edge->v = v ; edge->idx = idx ; edge->next = H[u] ; H[u] = edge ++ ; } void D_addedge ( int u , int v ) { edge->v = v ; edge->next = D[u] ; D[u] = edge ++ ; num[u] ++ ; } void bfs () { clr ( d , INF ) ; clr ( vis , 0 ) ; clr ( num , 0 ) ; head = tail = 0 ; d[s] = 0 ; Q[tail ++] = s ; while ( head != tail ) { int u = Q[head ++] ; vis[u] = 0 ; travel ( e , H , u ) { int v = e->v ; if ( d[v] > d[u] + 1 ) { d[v] = d[u] + 1 ; if ( vis[v] ) continue ; vis[v] = 1 ; Q[tail ++] = v ; } } } For ( i , 1 , m ) { int u = seg[i].u ; int v = seg[i].v ; if ( d[u] > d[v] ) swap ( u , v ) ; if ( d[v] > d[t] ) continue ; D_addedge ( d[v] , i ) ; } } void solve () { int u , v ; clear () ; For ( i , 1 , m ) { scanf ( "%d%d" , &u , &v ) ; addedge ( u , v , i ) ; addedge ( v , u , i ) ; seg[i].u = u ; seg[i].v = v ; } bfs () ; printf ( "%d\n" , d[t] ) ; For ( u , 1 , d[t] ) { printf ( "%d" , num[u] ) ; travel ( e , D , u ) { int v = e->v ; printf ( " %d" , v ) ; } printf ( "\n" ) ; } } int main () { while ( ~scanf ( "%d%d%d%d" , &n , &m , &s , &t ) ) solve () ; return 0 ; }
读懂题意很重要。。。
题目大意:给你一个字符集,每个字符均不同,字符集元素个数最多Z个(这个要自己计算)。
现在你有两个由字符集中的字符组成的串s1,s2,以及一个Z*Z的矩阵G。
现在你的任务是构造两个长度相同的串a、b,使得s1是串a的子序列,s2是串b的子序列。
并且这个串是有代价的!代价为两个串相同位置上的字符匹配所产生,匹配产生的代价来自矩阵G:
设串a第i个位置上的字符对应于字符集第posa个位置,串b第i个位置上的字符对应于字符集第posb个位置,则会产生代价G[posa][posb]。
现在你需要在满足串a、b长度相同,使得s1是串a的子序列,s2是串b的子序列的前提下使得产生的代价最小!
最后输出代价以及串a、b。
(题意好难描述啊。。一定是因为我语文是体育老师教的。。)
其实这题的模型就是我们熟知的最长上升子序列呀!
设dp[i][j]为串s1匹配到了第i个位置,串s2匹配到了第j个位置时的最小代价。
现在dp[i][j]可以转移到三个状态:dp[i+1][j]、dp[i][j+1]、dp[i+1][j+1]。
dp[i][j] -> dp[i+1][j]:说明dp[i+1][j]这个状态是因为在s2串中插入了一个字符转移而来的。此时这个字符一定与第s1串中第i+1个字符匹配的代价最小。
dp[i][j] -> dp[i][j+1]:说明dp[i][j+1]这个状态是因为在s1串中插入了一个字符转移而来的。此时这个字符一定与第s2串中第i+1个字符匹配的代价最小。
dp[i][j] -> dp[i+1][j+1]:说明dp[i+1][j+1]这个状态是因为s1串的第i+1个字符和s2串的第j+1个字符匹配转移而来的。
而转移时最小的代价是可以预处理的,转移时的前驱可以用结构体记录。
转移时,我们只取最小值的转移即可。
这样子最后得到的串a和串b一定是长度相同的,因为要么s1中的元素和插入到s2中的元素匹配,要么s1中插入的元素和s2中的元素匹配,要么s1和s2中的元素匹配。
代码如下:
#include <cstdio> #include <cstring> #include <algorithm> //#include <cmath> #include <queue> using namespace std ; typedef long long LL ; #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 travel( e , H , u ) for ( Edge* e = H[u] ; e ; e = e -> next ) #define clr( a , x ) memset ( a , x , sizeof a ) #define cpy( a , x ) memcpy ( a , x , sizeof a ) const int MAXN = 205 ; const int MAXM = 2005 ; const int INF = 0x3f3f3f3f ; struct Node { int n , m ; char c1 , c2 ; Node () {} Node ( int n , int m , char c1 , char c2 ) : n ( n ) , m ( m ) , c1 ( c1 ) , c2 ( c2 ) {} } pre[MAXM][MAXM] ; int G[MAXN][MAXN] ; int row_min[MAXN] ; int col_min[MAXN] ; char row_min_c[MAXN] ; char col_min_c[MAXN] ; int dp[MAXM][MAXM] ; char s[MAXM] , s1[MAXM] , s2[MAXM] ; int a[MAXM] , b[MAXM] ; char aa[MAXM << 1] , bb[MAXM << 1] ; int top1 , top2 ; int z , n , m ; void print ( int n , int m ) { if ( pre[n][m].n || pre[n][m].m ) print ( pre[n][m].n , pre[n][m].m ) ; aa[top1 ++] = pre[n][m].c1 ; bb[top2 ++] = pre[n][m].c2 ; } void solve () { top1 = top2 = 0 ; clr ( pre , 0 ) ; clr ( row_min , INF ) ; clr ( col_min , INF ) ; clr ( dp , INF ) ; z = strlen ( s + 1 ) ; scanf ( "%s%s" , s1 + 1 , s2 + 1 ) ; n = strlen ( s1 + 1 ) ; m = strlen ( s2 + 1 ) ; For ( i , 1 , n ) For ( j , 1 , z ) { if ( s1[i] == s[j] ) { a[i] = j ; break ; } } For ( i , 1 , m ) For ( j , 1 , z ) { if ( s2[i] == s[j] ) { b[i] = j ; break ; } } For ( i , 1 , z ) For ( j , 1 , z ) scanf ( "%d" , &G[i][j] ) ; For ( i , 1 , z ) For ( j , 1 , z ) { if ( row_min[i] > G[i][j] ) { row_min[i] = G[i][j] ; row_min_c[i] = s[j] ; } if ( col_min[j] > G[i][j] ) { col_min[j] = G[i][j] ; col_min_c[j] = s[i] ; } } dp[0][0] = 0 ; For ( i , 0 , n ) { For ( j , 0 , m ) { if ( dp[i][j] + row_min[a[i + 1]] < dp[i + 1][j] ) { pre[i + 1][j] = Node ( i , j , s1[i + 1] , row_min_c[a[i + 1]] ) ; dp[i + 1][j] = dp[i][j] + row_min[a[i + 1]] ; } if ( dp[i][j] + col_min[b[j + 1]] < dp[i][j + 1] ) { pre[i][j + 1] = Node ( i , j , col_min_c[b[j + 1]] , s2[j + 1] ) ; dp[i][j + 1] = dp[i][j] + col_min[b[j + 1]] ; } if ( dp[i][j] + G[a[i + 1]][b[j + 1]] < dp[i + 1][j + 1] ) { pre[i + 1][j + 1] = Node ( i , j , s1[i + 1] , s2[j + 1] ) ; dp[i + 1][j + 1] = dp[i][j] + G[a[i + 1]][b[j + 1]] ; } } } print ( n , m ) ; printf ( "%d\n" , dp[n][m] ) ; rep ( i , 0 , top1 ) printf ( "%c" , aa[i] ) ; printf ( "\n" ) ; rep ( i , 0 , top2 ) printf ( "%c" , bb[i] ) ; printf ( "\n" ) ; } int main () { while ( ~scanf ( "%s" , s + 1 ) ) solve () ; return 0 ; }
这个是我队友一人做的。。用的是辛普森公式。
恩恩。。就是这样。。