ZOJ Monthly, June 2014 部分题解

来源:互联网 发布:c语言windows.h库函数 编辑:程序博客网 时间:2024/05/18 22:40

难过 一个下午都在作死 ... 没有一题顺的 ... 各种算法出来了也死活敲坑掉了


zoj 3789 Gears(并查集)


题意 : 有n个齿轮 , 每个齿轮都往一个方向旋转 。对于齿轮有如下几种操作 :

1. L u v 把齿轮 u 和 齿轮 v 连接并成一组 , 两个齿轮可以连接表示齿轮的旋转方向不同 。

2. D u 把齿轮u 从原来的组中删除

3. Q u v , 询问两个齿轮的旋转的方向相同,不同,还是不知道

4. S u , 询问 u 所在的齿轮组的齿轮数


思路 :

这题就是各种类型并查集的大杂烩 ... 好在都是各种简单并查集的合并

对于连接操作 ,是明显的种类并查集 ,只有两个种类 , 向左转的 , 向右转的 。

我用了一个辅助数组Opp[] , Opp[i] 表示与 齿轮 i 方向相反的齿轮 , 初始化的时候 Opp[i] = i

那么合并 u , v 的时候 , 如果Opp[u] = u , 说明Opp[u] 之前没有操作过 , 那么就 Opp[u] = v

否则 , 说明 u 之前已经合并过了 , 那么就把 Opp[u] 和 v 合并到一个集合中去

另外这里合并的结合不是齿轮所在组的集合 , 需要重新开一个数组表示方向的集合

删除操作的话 , 只要重新新建一个结点代替被删掉 , 然后把原来组的个数删掉一个就行了。

查询操作就判断 u , v 是不是在同一个集合( 表示方向的那个集合 ) 就行了 ,当然 , 我们要先判断u,v是不是在同一组里面,不是的话,就不能确定了( 不同组的两个肯定不在一个集合(表示方向)里的,但方向可能是相同的 )

查询齿轮数的操作 ,简单的带权就行了


#include <stdio.h>#include <string.h>#include <algorithm>using namespace std;#define MAXN 600005int Index[MAXN] ;int Set[MAXN] , Rank[MAXN] , Opp[MAXN] , Rotation[MAXN];void init( int n ) {for( int i = 1 ; i <= n ; i ++ ) {Set[i] = i ;Rank[i] = 1 ;Opp[i] = i ;Index[i] = i ;Rotation[i] = i ;}}int find( int x , int * Set ) {return x == Set[x] ? Set[x] : Set[x] = find( Set[x] , Set ) ;}void Union1( int x , int y ) {int fx = find( x , Set ) , fy = find( y , Set ) ;if( fx == fy ) return ;Set[fx] = fy ;Rank[fy] += Rank[fx] ;}void Union2( int x , int y ) {int fx = find( x , Rotation ) , fy = find( y , Rotation ) ;if( fx == fy ) return ;Rotation[fx] = fy  ;}int main(){int n , m ;while( scanf( "%d%d" , &n , &m )!= EOF ) {init( n + m ) ;int Nexid = n + 1 ;char op[5] ;while( m -- ) {scanf( "%s" , op ) ;if( op[0] == 'L' ) {int a , b ;scanf( "%d%d" , &a , &b ) ;a = Index[a] , b = Index[b] ;if( find( a , Rotation ) == find( b , Rotation ) ) {continue ;}Union1( a , b ) ;if( Opp[a] == a ) {Opp[a] = b ;}else {Union2( Opp[a] , b ) ;}if( Opp[b] == b ) {Opp[b] = a ;}else {Union2( Opp[b] , a ) ;}}else if( op[0] == 'D' ){int u ;scanf( "%d" , &u ) ;int fu = find( Index[u] , Set ) ;Rank[fu] -- ;Index[u] = Nexid ++ ;}else if( op[0] == 'Q' ) {int u , v ;scanf( "%d%d" , &u , &v ) ;u = Index[u] , v = Index[v] ;if( find( u , Set) != find( v , Set ) ) {puts( "Unknown" ) ;} else {int fu = find( u , Rotation ) , fv = find( v , Rotation ) ;if( fu == fv ) {puts( "Same" ) ;} else {puts( "Different" ) ;}}}else {int u ;scanf( "%d" , &u ) ;u = Index[u] ;int fu = find( u , Set ) ;printf( "%d\n" , Rank[fu] ) ;}}}return 0 ;}

zoj 3790Consecutive Blocks (二分)

题意 : 给你 n 个方块 , 每个方块都有一种颜色 , 你最多删除k个方块 , 问删除之后颜色相同的连续方块最大有多少 ?

思路 : 先将颜色离散化 , 然后把开了10w个vector , 把每个方块都扔到对应的vector里面 , 存在里面的方块的下标 。 然后枚举终点 , 对于每个终点 , 我们二分对应的vector即可


#include <stdio.h>#include <string.h>#include <algorithm>#include <vector>using namespace std ;int a[100005] , b[100005] ;vector<int> ans[100005] ;int lisanhua( int * x , int n ) {int Index = 1 ;for( int i = 2 ; i <= n ; i ++ ) {if( x[i] != x[i-1] ) {x[++Index] = x[i] ;}}return Index ;}int binsearch( int l , int r , int * x , int key ) {int m ;while( l <= r ) {m = ( l + r ) >> 1 ;if( x[m] == key ) return m ;else if( x[m] > key ) {r = m - 1 ;}else{l = m + 1 ;}}return -1 ;}int main(){int n , k ;while( scanf( "%d%d" , &n , &k ) != EOF ) {for( int i = 1 ; i <= n; i ++ ) {scanf( "%d" , &a[i] ) ;}memcpy( b , a , sizeof(a) ) ;sort( b + 1 , b + 1 + n ) ;int Index = lisanhua( b , n ) ;for( int i = 1 ; i <= Index ; i ++ ) ans[i].clear() ;int Max = 0 ;for( int i = 1 ; i <= n ; i ++ ) {int id = binsearch( 1 , Index , b , a[i] ) ;ans[id].push_back( i ) ;int size = ans[id].size() - 1 ;int l = 0 , r = size ;int m ;while( l < r ) {int m = ( l + r ) >> 1 ;if( i - ans[id][m] - size + m <= k ) {r = m ;}else{l = m + 1 ;}}Max = max( Max , size - l + 1 ) ;}printf( "%d\n" , Max ) ;}return 0 ;}

zoj 3791An Easy Game ( 动态规划 )

题意 : 给你两个01串 , 你需要经过k次 , 每次取反m个字符 , 从第一个字符串得到第二个字符串的方案数

思路 : dp[i][j] 表示前 i 个字符串有j个匹配的方案数 , 转移的时候只要枚举有你这次要取反几个已经匹配了的 , 在利用组合数转移即可


#include <stdio.h>#include <string.h>#include <algorithm>using namespace std;#define mod 1000000009#define N 105long long dp[105][105] ;long long CC[105][105] ;  void Init () {      int i,j;      for (i=0;i<=100;i++) {          CC[i][0]=CC[i][i]=1;          for (j=1;j<i;j++)              CC[i][j]=(CC[i-1][j-1]+CC[i-1][j])%mod;      }  }  char str1[105] , str2[105] ;int main(){int n , m , k ;Init() ;while( scanf( "%d%d%d" , &n , &k , &m ) != EOF ) {scanf( "%s%s" , str1 , str2 ) ;int zero = 0 ;for( int i = 0 ; i < n ; i ++ )if( str1[i] == str2[i] ) zero ++ ;memset( dp , 0 , sizeof(dp) ) ;dp[0][zero] = 1 ;for( int i = 0 ; i < k ; i ++ ) {for( int j = 0 ; j <= n ; j ++ ) {if( dp[i][j] ) {for( int l = 0 ; l <= m ; l ++ ) {int z = l , o = m - l ;if( z > j || o > n - j ) continue ;int nez = j - z + o ; dp[i+1][nez] = dp[i+1][nez] + (( dp[i][j] * CC[j][z])%mod * CC[n-j][o] )%mod ;if( dp[i+1][nez] >= mod ) dp[i+1][nez] -= mod ;}}}}printf( "%lld\n" , dp[k][n] ) ;}return 0 ;}

zoj 3792Romantic Value ( 最小割 )

题意 : 求图的最小割 ,但要求边数最小

思路 : 处理很简单 , 把每条边的流量 * 一个大于边数的值 + 1 即可 , 我这里选的是2000 , 那么最后 ans % 2000 就是边数 , ans / 2000 就是最小割


#include <stdio.h>#include <string.h>#include <algorithm>using namespace std ;#define MAXN 1005#define MAXM 100005#define INF 0x3f3f3f3fstruct Graph{int Index ;int head[MAXN] ;struct Edge{int to , flow , nex ;Edge(){}Edge( int To , int Flow , int Nex ):to(To),flow(Flow),nex(Nex){};}edge[MAXM*2] ;void init(){Index = 0 ;memset(head,-1,sizeof(head));}void add( int from , int to , int f ){edge[Index] = Edge( to , f , head[from] ) ;head[from] = Index ++ ;}}gra;// 预处理int n , m , s , t ;  // 顶点数 , 边数 , 起点 , 终点int level[MAXN] ; // 层次图层数int q[MAXN] ;int cur[MAXN] ; // 当前弧优化// 有向边void insert1( int from , int to , int flow ){gra.add( from , to , flow );gra.add( to , from , 0 );}// 无向边void insert2( int from , int to , int flow ){gra.add( from , to , flow ) ;gra.add( to , from , flow ) ;}// 构造层次网络bool bfs(){memset(level,0,sizeof(level));level[s]=1;int top = 0 , rear = 0 ;q[rear++]=s ;while(top!=rear){int tmp = q[top++] ;if( tmp == t )return true;for( int e = gra.head[tmp] ; ~e ; e = gra.edge[e].nex ){int to = gra.edge[e].to , f = gra.edge[e].flow ;if( level[to] || !f) continue ;level[to] = level[tmp] + 1 ;q[rear++]=to;}}return false;}// 多路增广int dfs( int u , int a ) {if( u == t || a == 0 ) return a ;int flow = 0 , f ;for( int & e = cur[u] ; ~e ; e = gra.edge[e].nex ){  // 当前弧优化int to = gra.edge[e].to ;if( level[u] + 1 == level[to] && ( f = dfs( to , min( a , gra.edge[e].flow )))>0 ){gra.edge[e].flow -= f ;gra.edge[e^1].flow += f ;flow += f ;if( !(a -= f) )break;}}if( !flow ) level[u] = -1 ;   // -1 优化return flow ;}int dinic(){int ans = 0 ;while( bfs() ) {memcpy( cur , gra.head , sizeof(cur) ) ;ans += dfs( s , INF ) ;}return ans ;}int main(){int cas ;scanf( "%d" , &cas ) ;//int s , t ;while( cas -- && scanf("%d%d%d%d",&n,&m,&s,&t) !=EOF ){gra.init() ;int sum = 0;for( int i=1;i<=m;i++ ){int a, b , f ;scanf("%d%d%d",&a,&b,&f) ;sum += f ;insert2( a , b , f * 2000 + 1 );}int ans = dinic() ;if( ans == 0 ) {puts( "Inf" ) ;}else{printf("%.2lf\n",1.0*(sum-ans/2000)/(ans%2000));}}return 0 ;}

zoj 3793First Digit

题意 : 给你 n 个case , 每个case 一个b , e 你需要求b^e的首个数字 。但奇怪的要求是正确率要在 25 % ~ 60 %

思路 : 恩 ... 这题是全场最简单的题 ... 也是作死的开始 ... 开始老老实实的算了半天 , 样例都出来了,但是答案总是wa ... 或许是精度问题 ? 恼怒之下交了个 puts("1") ; AC了 ! ! 擦 ... 回头一想题目这么长也就是说1的概率大致是这个范围吧 ... 真是作死,还手算委屈


... 代码不贴了


zoj 3794Greedy Driver ( 最短路 )

题意 : 有人要从 city 1 走到 city n ,有一个容量为C的油箱 , 一开始是满的 , 每条路都有权值 ,表示走过这条路花费的油 。有一些城市有加油站,可以免费加满油 ,一些城市有卖油的地方 ,每个地方的油价不同 , 但是主人公只能买一次油。 问主人公在能到n的前提下,最多能靠买油赚多少钱。

思路 : 两遍最短路 , 第一次从起点出发 , 计算起点到达每个点时能剩下最多的油。第二次从终点出发 ,计算从每个点出发能到终点的最少油价。 然后在枚举每个能卖油的city计算下即可。难过 注意是有向边 ... 一直在逗 ...


#include <stdio.h>#include <string.h>#include <queue>#include <algorithm>using namespace std ;#define MAXN 1005#define MAXM 100005#define INF 0x3f3f3f3fint n , m , c ;int p , q ;struct Graph{struct Edge{int to , nex , val ;Edge(){}Edge( int _to , int _val , int _nex ){to = _to ;val = _val ;nex = _nex ;}}edge[MAXM] ;int head[MAXN] ;int Index ;void init(){Index = 0 ;memset( head , -1 ,sizeof(head) ) ;}void add( int from , int to , int val ) {edge[Index] = Edge( to , val , head[from] ) ;head[from] = Index ++ ;}}gra , gra2 ;int buy[1005] ;int sell[1005] , v[1005] ;int dist1[1005] ;bool inq[1005] ;int dist2[1005] ;queue<int> Q ;void spfa1( int s ) {dist1[s] = c ;inq[s] = true ;Q.push(s) ;while( !Q.empty() ) {int tmp = Q.front() ; Q.pop() ; inq[tmp] = false ;for( int i = gra.head[tmp] ; ~i ; i = gra.edge[i].nex ) {int to = gra.edge[i].to ;if( dist1[tmp] >= gra.edge[i].val ) {int Max = dist1[tmp] - gra.edge[i].val ;if( buy[to] ) Max = c ;if( dist1[to] < Max ) {dist1[to] = Max ;if( !inq[to] ) {inq[to] = true ;Q.push( to ) ;}}}}}}void spfa2( int s ) {dist2[s] = 0 ;inq[s] = true ;Q.push(s) ;while( !Q.empty() ) {int tmp = Q.front() ; Q.pop() ;inq[tmp] = false ;for( int i = gra2.head[tmp] ; ~i ; i = gra2.edge[i].nex ) {int to = gra2.edge[i].to , val = gra2.edge[i].val ;if( dist2[tmp] + val <= c ) {int Min = dist2[tmp] + val ;if( buy[to] ) Min = 0 ;if( dist2[to] > Min ) {dist2[to] = Min ;if( !inq[to] ) {inq[to] = true ;Q.push( to ) ;}}}}}}int main(){while( scanf( "%d%d%d" , &n , &m , &c ) != EOF ) {gra.init() ; gra2.init() ;for( int i = 1 ; i <= m ; i  ++ ) {int from , to , val ;scanf( "%d%d%d" , &from , &to , &val ) ;gra.add( from , to , val ) ;gra2.add( to , from , val ) ;}scanf( "%d" , &p ) ;memset( buy , 0 , sizeof(buy) ) ;for( int i = 1 ; i <= p ; i ++ ) {int t ;scanf( "%d" , &t ) ;buy[t] = 1 ;}scanf( "%d" , &q ) ;for( int i = 1 ; i <= q ; i ++ ) {scanf( "%d%d" , &sell[i] , &v[i] ) ;}for( int i = 1 ; i <= n ; i ++ ) dist1[i] = -INF , dist2[i] = INF ;memset( inq , false , sizeof(inq) ) ;spfa1( 1 ) ;if( dist1[n] == -INF ) {puts( "-1" ) ;continue ;}memset( inq , false , sizeof(inq) ) ;spfa2( n ) ;int Max = 0 ;for( int i = 1 ; i <= q ; i ++ ) {int t = sell[i] ;if( dist1[t] >= dist2[t] ) {Max = max( Max , ( dist1[t] - dist2[t] ) * v[i] ) ;}}printf( "%d\n" , Max ) ;}return 0 ;}

zoj 3795Grouping(强连通缩点)

题意 : 有 n 个人 , m条信息 。 每条信息给你两个人 u ,v , 表示u 的年龄大于等于v ,需要分成几组,让每组中的人不能通过直接或者间接关系得到任意两个人的年龄关系。求最少分几组。

思路 : 很显然如果这个图没有环的话,只要从入度为0的点开始深搜求个最大深度就行了 , 每个深度放在同一组 。 但是因为他们表示的是大于等于的关系 , 所以年龄相同的几个人当中可能会产生环  , 那么只要强连通缩点就可以了 。大哭 脑残的把缩点之后的点当一个人算了 ... 然后又悲剧了 ... 悲剧着悲剧着 ... 比赛就结束了 ...

#include <stdio.h>#include <string.h>#include <algorithm>using namespace std;//#define INF 0x3fffffff#define MAXN 100005#define MAXM 300005// 图struct Graph{int head[MAXN] ;int Index ;struct Edge{int to , nex ;Edge(){}Edge( int _to , int _nex ):to(_to),nex(_nex){}}edge[MAXM] ;void Init(){Index = 0 ;memset( head , -1 , sizeof(head) ) ;}void add( int from , int to ){edge[Index] = Edge( to , head[from] ) ;head[from] = Index++ ;}}gra,now; // gra 原图 , now 新图int belong[MAXN] ; // 每个点所属的强连通分量int sta[MAXN] , top ; // 栈 用于计算哪些点属于哪些强连通分量int cntp[MAXN] , cnte[MAXN]; // 统计每个强连通分量的点的个数 , 以及每个强连通分量的边的个数int low[MAXN] , dfn[MAXN] ; // dfn[u] u点的时间戳 , low[u] u以及u的孩子节点通过回边能达到的最小时间戳int Bcnt ; // 强连通分量个数int Index  ; // 时间戳int n , m ; // 顶点个数 , 边个数void tarjan( int u ){dfn[u] = low[u] = ++Index ;sta[top++] = u ;for( int i = gra.head[u] ; ~i ; i = gra.edge[i].nex ){int v = gra.edge[i].to ;if( !dfn[v] ){tarjan( v ) ;low[u] = min( low[u] , low[v] ) ;}else if( !belong[v] )low[u] = min( low[u] , dfn[v] ) ;}if( dfn[u] == low[u] ){Bcnt++ ;int tmp ;do{cntp[Bcnt]++;tmp = sta[--top] ;belong[tmp] = Bcnt ;}while( tmp != u ) ;}}// 计算强连通分量个数 , 并且计算每个点属于哪些连通分量void solve(){Index = top = Bcnt = 0 ;memset(dfn,0,sizeof(dfn));memset(belong,0,sizeof(belong));memset(cntp,0,sizeof(cntp));for( int i=1;i<=n;i++ ){if( !dfn[i] )tarjan(i);}}int in[MAXN],out[MAXN],flag[MAXN] ; // 每个强连通分量的入度,出度 以及 判重边 //  缩点建新图,顺便统计强连通分量的入度和出度,每个强连通分量的边数void buildnew(){memset(in,0,sizeof(in));memset(out,0,sizeof(out));memset(cnte,0,sizeof(cnte));memset(flag,-1,sizeof(flag)) ;now.Init(); // 初始化新图for( int u=1;u<=n;u++ ){for( int i=gra.head[u];~i;i=gra.edge[i].nex ){int v = gra.edge[i].to ;// 用flag防止加入重边 , 是否需要根据具体题目的需求而定if( belong[u]!=belong[v] && flag[belong[v]]!= belong[u] ){flag[belong[v]] = belong[u] ;now.add( belong[u] , belong[v] ) ;in[belong[v]]++;out[belong[u]]++;}else if(belong[u] == belong[v] ){cnte[belong[u]]++;}}}}int Max ;int vv[MAXN] ;int dfs( int u ) {if( vv[u] != -1 ) return vv[u] ;int Max = 0 ;for( int i = now.head[u] ; ~i ; i = now.edge[i].nex ) {int to = now.edge[i].to ;Max = max( dfs( to ) , Max ) ;}return vv[u] = Max + cntp[u] ;}int main(){while( scanf( "%d%d" , &n , &m ) != EOF ) {gra.Init() ;for( int i = 1 ;i <= m ; i++ ) {int a , b ;scanf( "%d%d" , &a , &b ) ;gra.add( a, b ) ;}solve() ;buildnew() ;Max = 0 ;memset( vv , -1 , sizeof(vv) ) ;for( int i = 1 ; i <= Bcnt ; i ++ ) {if( in[i] == 0 ) {dfs( i ) ;Max = max( Max , vv[i] ) ;}}printf( "%d\n" , Max ) ;}return 0 ;}

0 0