NBUT 2014 校赛 (Minecraft专场)

来源:互联网 发布:钢联数据3.0 编辑:程序博客网 时间:2024/04/19 07:57

A 题 Minecraft Server Bug


http://acm.nbut.edu.cn/Problem/view.xhtml?id=1552

题意 : 给你一串W,L串,你需要找一对W,L,要求W在L之前,能找到多少对。

思路 : 直接遍历字符串 , 用一个变量记录之前出现过多少W了,遇到L就加上这个数量就行。


#include <stdio.h>int main(){int n ;while( scanf( "%d" , &n ) != EOF ) {long long ans = 0 ;long long cntW = 0 ;char str[3] ;while( n -- ) {scanf( "%s" , str ) ;if( str[0] == 'W' ) cntW ++ ;else ans += cntW ;}printf( "%lld\n" , ans ) ;}return 0 ;}

B 题 Beautiful Walls

http://acm.nbut.edu.cn/Problem/view.xhtml?id=1553

题意: 给你一堆unit( 就是墙吧 ? 这游戏没玩过 ) , 每个unit有高度 , 你需要选择一个区间 [ a , b ] , 使这个区间中的unit没有高度相同的。问你有多少种选择的方法。

思路: 枚举区间的终点 , 对于每个终点 , 我们只要知道起点最前面可以是多少就行了。那么如何确定这个最前面的起点,很显然,如果当前枚举的这个高度之前已经出现过了,那么起点一定在上次出现这个高度的unit之后 , 那么我们规定 pre[i] 为和第i个unit高度相同的unit的下标。那么对于以一个终点,那么他的最前面的起点就是max( pre[j] | j < =i ) + 1 了。为什么取pre[j]的最大值? 确定pre[i]的话 , 可以用map或者hash记录下就可以了。


#include <stdio.h>#include <string.h>#include <algorithm>#include <map>using namespace std ;map<int,int> last ;int main(){int n ;while( scanf( "%d" , &n ) != EOF ) {last.clear() ;long long ans = 0 ;int lll = 0 ;for( int i = 1 ; i <= n ; i ++ ) {int a  ;scanf( "%d" , &a ) ;int ll = last[a] ; last[a] = i ;lll = max( lll , ll ) ;ans += i - lll ;}printf( "%lld\n" , ans ) ;}return 0 ;}


C 题 Lord of Minecraft LCA

http://acm.nbut.edu.cn/Problem/view.xhtml?id=1554

题意: 给你一棵树,问从a点跑到b点,会不会经过和a点同样深度的点,如果b点的深度和a点相同,也算经过。

思路: 其实就是判断a和b是不是祖孙关系,如果是的话,肯定不会经过,不会的话,比较深度就可以了

那么判断a和b是不是祖孙关系,通常就是条件反射求LCA。

出题人给了另外一个思路,就是直接dfs的时候,计算进入每个顶点和离开每个顶点的时间戳,如果a和b是祖孙关系,那么a和b的时间戳区间必然是一个区间包含另一个区间。

下面的代码的话,是用LCA的。


#include <stdio.h>#include <string.h>#include <math.h>#include <stdlib.h>#include <map>#include <string>#include <algorithm>using namespace std;#define MAXN 10005#define MAXM 10005int n , m , q ;map<string,int> mp ;struct Graph{    int head[MAXN] ;    int Index ;    struct Edge{        int to , nex , val ;        Edge(){}        Edge( int _to , int _nex , int _val ):to(_to),nex(_nex),val(_val){}     }edge[MAXM*2] ;    void init(){        memset(head,-1,sizeof(head));        Index = 0 ;    }    void add( int from , int to , int val = 0 ){        edge[Index] = Edge( to , head[from] , val ) ;        head[from] = Index ++ ;    }}gra;int degree[MAXN] ;int E[MAXN*2] ;    // dfs的下标int R[MAXN] ;       // 每个时间戳所对应的节点int H[MAXN*2] ;      // E中对应节点的深度bool vis[MAXN] ;   // 某个节点是否被访问int id ;           // E 数组对应的下标 , 初始值为 0int dis[MAXN] ;    // 点u距离根节点的距离(边有边权的情况下)int Index ;void dfs( int u , int d , int dist ){ // dfs 时的节点u , 离根节点的距离d     E[++id] = u ; R[u] = id ; H[id] = d ; vis[u] = true ; dis[u] = dist ;     for( int i = gra.head[u] ; ~i ; i = gra.edge[i].nex ){        int ch = gra.edge[i].to ;        if( !vis[ch] ){            dfs( ch , d + 1 , dist + gra.edge[i].val ) ;            E[++id] = u ; H[id] = d ;        }     }}int stmin[MAXN*2][21] ; // 注意!!存的是最小值的下标,不是最小值int d[21] ; // d[i] = 1 << i 用于加快计算速度// 预处理RMQvoid InitRMQ(){    d[0]=1;    for( int i=1;i<21;i++ )d[i] = d[i-1]<<1 ;    for( int i=1;i<=id;i++ ) stmin[i][0] = i ;    for( int j=1;d[j]<=id;j++ )        for( int i=1;i+d[j]-1<=id;i++ )            stmin[i][j] = H[stmin[i][j-1]]<H[stmin[i+d[j-1]][j-1]]?stmin[i][j-1]:stmin[i+d[j-1]][j-1] ; }// 构完树之后直接调用记得可 , 可以解决存在森林的情况void solve(){    id = 0 ;    memset(vis,false,sizeof(vis));    //dfs(1,0,0);for( int i = 1 ; i <= Index ; i ++ ) {if( degree[i] == 0 ) {dfs( i , 0 , 0 ) ;break;}}/*for( int i=1;i<=Index;i++ ){        if(!vis[i]){            while( true ) ;        }    }*/    InitRMQ();}// 若LCA == -1 则说明点a和点b在不同的连通块int LCA( int a , int b ){ // 返回LCA(a,b)    int l = R[a] , r = R[b] ;    if( l > r )swap( l , r ) ;    int k = int( log(double(r-l+1))/log(2.0) );    int lca = H[stmin[l][k]]<H[stmin[r-d[k]+1][k]]?stmin[l][k]:stmin[r-d[k]+1][k] ;    return E[lca] ;}int main(){int casn = 0 ;while( scanf("%d%d",&n,&q)!=EOF ){casn ++ ;//if( casn == 3 ) while( true ) ;        gra.init();mp.clear() ;Index = 0 ;memset( degree , 0 , sizeof(degree) ) ;        for( int i=1;i<=n;i++ ){char name1[105] , name2[105] ;scanf( "%s%s" , name1 , name2 ) ;int ida = mp[name1], idb = mp[name2] ;if( ida == 0 ) {ida = mp[name1] = ++Index ;}if( idb == 0 ) {idb = mp[name2] = ++Index ; }            gra.add(ida,idb,1);            gra.add(idb,ida,1);degree[ida] ++ ;        }        solve();int ans = 0 ;        while(q--){char name1[105] , name2[105] ;scanf( "%s%s" , name1 , name2 ) ;int ida = mp[name1] , idb = mp[name2] ;//if( ida == 0 || idb == 0 ) {//while( true ) ;//}            int lca = LCA(ida,idb);if( lca == ida || lca == idb ) {continue ;}if( dis[ida] <= dis[idb] ) ans ++ ;            //printf("%d\n",dis[a]+dis[b]-2*dis[lca]);        }printf( "%d\n" , ans ) ;    }    return 0 ;}

D 题 The Sum of F(x) and G(x)

http://acm.nbut.edu.cn/Problem/view.xhtml?id=1555

题意: 就是模拟两个多项式相加

思路: 数据量很小,怎么搞都行


#include <stdio.h>#include <string.h>#include <algorithm>using namespace std;int c[30] ;int main(){int n , m ;while( scanf( "%d%d" , &n , &m ) != EOF ) {memset( c , 0 , sizeof(c) ) ;while( n -- ) {int a , b ;scanf( "%d%d" , &a , &b ) ;c[b+15] += a ; }while( m -- ) {int a  , b ;scanf( "%d%d" , &a , &b ) ;c[b+15] += a ;}for( int i = 25 ; i >= 0 ; i -- ) {if( c[i] ) {printf( "%d %d\n" , c[i] , i - 15 ) ;}}}return 0 ;}

E 题 Flandre at Minecraft

http://acm.nbut.edu.cn/Problem/view.xhtml?id=1556

题意 : 等于给你个N*N的01串,你要通过最小的操作数把他变成全0串,你可以有三种操作,

1: 取反一个元素

2. 取前D*K个元素取反

3. 取后D*K个元素取反

D是题目给出的,并保证能被N*N整除,K是你任意选取的,并每次操作可以不一样。

求最小操作数。


思路 : 还没想出来 ... 想出来再补 ...


F 题 Team of Slime

http://acm.nbut.edu.cn/Problem/view.xhtml?id=1557

题意: 给你一个1~N的排列,你要通过一些操作把这个排列变成1,2,...,N这样的有序排列。但是每次操作只能把某个数提到数列的最前面。问最小操作数

思路: 很明显,如果 a[i] > a[j] 并且 i < j , 那么如果最后要让他们变得有序,肯定是要有某次操作把a[j]提到前面去的。那么这样之后,所有小于a[j]都必须往前提一遍,也只需要提一遍(只要从a[j]-1,a[j]-2...,1这样的顺序往前提) , 就能让1...a[j]是有序的在前j个了。那么我们还可以想到,如果a[j]比较小,这样操作完,当有比他大的往前提的时候,这个操作就全白费了,所以我们很容易就可以想到,第一个往前提的就是满足上述关系的最大的一个a[j] ,并且最后答案就是a[j]。那么这样我们只要记录每个元素出现的位置就可以了。


#include <stdio.h>#include <string.h>#include <algorithm>using namespace std;int pos[300005] ;int main(){int n; while( scanf( "%d" , &n ) != EOF ) {int a ;                for( int i = 1 ;i <= n ;i ++ ) {scanf( "%d" , &ai ) ; pos[a] = i ;}int ans = 0 ;int Min = n ;for( int i = n ; i >= 1 ; i -- ) {if( pos[i] > Min ) {ans = i ;break;}else{Min = min( Min , pos[i] ) ;}}printf( "%d\n" , ans ) ;} return 0 ;}

G题 Racing Cheat 计算几何 + 最短路

http://acm.nbut.edu.cn/Problem/view.xhtml?id=1558

题意 : 起点是( 0 , 0 ) , 终点是( n , n ) , 然后给你n条线段作为障碍物,求从起点到终点的最短路。

思路: 把起点终点还是线段的端点都处理到邻接矩阵中,两点之间没有线段阻拦的就连上权值为长度的边,跑遍flody就行了


#include <stdio.h>#include <string.h>#include <algorithm>#include <math.h>using namespace std ;#define POW(x) ((x)*(x))#define eps 1e-8#define zero(x) (((x)>0?(x):-(x))<eps)#define INF 1e16int n , m , peo ;struct Point{    double x, y ;    Point(){}    Point( double X ,double Y ):x(X),y(Y){}}p[305];struct Line{    Point a , b ;    Line(){}    Line(Point A , Point B):a(A),b(B){}}l[105];double Map[305][305] ;double Dist( Point a , Point b ){    return sqrt( POW(a.x-b.x) + POW(a.y-b.y));}double xmult(Point p1,Point p2,Point p0){    return(p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y);}int opposite_side(Point p1,Point p2,Line l){    return xmult(l.a,p1,l.b)*xmult(l.a,p2,l.b)<-eps;}int intersect_ex(Line u,Line v){    return opposite_side(u.a,u.b,v) && opposite_side(v.a,v.b,u);}int cntP ;int main(){  double ll ;    while(  scanf("%lf%d",&ll,&m) != EOF ){        cntP = 0 ;n = 2 ;p[++cntP] = Point( 0 , 0 ) ;p[++cntP] = Point( ll , ll ) ;        for( int i=1;i<=m;i++ ){            double a , b , c , d ;            scanf("%lf%lf%lf%lf",&a,&b,&c,&d);            p[++cntP] = Point(a,b);            p[++cntP] = Point(c,d);            l[i] = Line(p[cntP-1],p[cntP]);        }                int N = n + 2 * m ;        for( int i=1;i<=N;i++ ){            for( int j=1;j<=N;j++ )                Map[i][j] = INF ;            Map[i][i]=0;        }        for( int i=1;i<=N;i++ ){            for( int j=i+1;j<=N;j++ ){                bool ok = true ;                for( int k=1;k<=m;k++ ){                    if(intersect_ex(Line(p[i],p[j]),l[k])){                        ok = false ;break;                    }                }                if( ok ){                    Map[i][j] = Map[j][i] = Dist(p[i],p[j]);                }            }        }        for( int k=1;k<=N;k++ )            for( int i=1;i<=N;i++ )                for( int j=1;j<=N;j++ )                    if( i!=j && j!=k && i!=k && Map[i][k] + Map[k][j] < Map[i][j] )                        Map[i][j] = Map[i][k] + Map[k][j] ;                printf("%.2lf\n", Map[1][2] );    }    return 0 ;}

H题 Jump to the Top of Mountain dfs

http://acm.nbut.edu.cn/Problem/view.xhtml?id=1559

题意: 给你一张图,每个点都有高度,外围又一圈高度为0的地方,你可以选择外围任意的点为起点,你只能往四个方向走,并且你当前处于高度x的话,你不能走到高度大于x+1的格子,问你能不能走到最高峰。( PS : 貌似最高峰有多个 , 只要能走到其中一个就可以了 )

思路: 图很小,只要从最高点往外dfs,看看能不能走到边界就行了


#include <stdio.h>#include <string.h>#include <algorithm>using namespace std;int n , m ;int Max ;int mp[105][105] ;bool vis[105][105] ;int dir[4][2] = { 1 , 0 , -1 , 0 , 0 , 1 , 0 , -1 } ;bool ok ;void dfs( int x , int y ) {if( x < 0 || x > n || y < 0 || y > m ) {ok = true ;return ;}vis[x][y] = true ;for( int i = 0 ; i < 4 ; i ++ ) {int nex = x + dir[i][0] , ney = y + dir[i][1] ;if( vis[nex][ney] ) continue ;if( mp[nex][ney] + 1 >= mp[x][y] ) {dfs( nex , ney ) ;if( ok ) return ;}}}int main(){while( scanf( "%d%d" , &n , &m ) != EOF ) {Max = 0 ;memset( mp , 0 , sizeof(mp) ) ;for( int i = 1 ;i <= n ;i ++ ) {for( int j = 1 ; j <= m ; j ++ ) {scanf( "%d" , &mp[i][j] ) ;Max = max( Max , mp[i][j] ) ;}}memset( vis , false , sizeof(vis) ) ;ok = false ;for( int i = 1 ; i <= n && ok == false ; i ++ ) {for( int j = 1 ; j <= m && ok == false ; j ++ ) {if( mp[i][j] == Max ) {dfs( i , j ) ;}}}puts( ok?"YES":"NO") ;}return 0 ;}

I 题 Let Slimes Grow Up 线段树

http://acm.nbut.edu.cn/Problem/view.xhtml?id=1560

题意 : 给你n个数,有两个操作,一个是区间[a,b]所有数加上c,另一个是查询[a,b]区间中的方差大小。

思路: 主要只要求解出 [a,b] 区间中 Σ( x ^ 2 ) 和 Σ ( x ) 就可以了。求Σ(x) 直接线段树区间更新,区间查询即可。Σ( x^2 ) 在更新的时候要通过( x + c ) ^ 2 = ( x ^ 2 + 2 * c * x + c ^2 ) 这个公式维护即可。

嘛...这题貌似因为是精度问题...一直过不了... 下面是代码。在OJ上AC不了


#include <stdio.h>#include <string.h>#include <algorithm>using namespace std;#define lson l , m , rt << 1 #define rson m + 1 , r , rt << 1 | 1 #define MAXN 100005long long sum1[MAXN<<2] , sum2[MAXN<<2] , lazy[MAXN<<2] ;void pushup( int rt ) {sum1[rt] = sum1[rt<<1] + sum1[rt<<1|1] ;sum2[rt] = sum2[rt<<1] + sum2[rt<<1|1] ;}void build( int l , int r , int rt ) {lazy[rt] = 0 ;if( l == r ) {scanf( "%lld" , &sum1[rt] ) ;sum2[rt] = sum1[rt] * sum1[rt] ;return ;}int m = (  l + r ) >> 1 ;build( lson ) ;build( rson ) ;pushup( rt ) ;}void pushdown( int rt , int len ) {lazy[rt<<1] += lazy[rt] ;lazy[rt<<1|1] += lazy[rt] ;sum2[rt<<1] += 2 * lazy[rt] * sum1[rt<<1] + lazy[rt] * lazy[rt] * ( len - len / 2 ) ;sum2[rt<<1|1] += 2 * lazy[rt] * sum1[rt<<1|1] + lazy[rt] * lazy[rt] * ( len / 2 ) ;sum1[rt<<1] += lazy[rt] * ( len - len / 2 ) ;sum1[rt<<1|1] += lazy[rt] * ( len / 2 ) ;lazy[rt] = 0 ;}void update( int l , int r , int rt , int L , int R , int add ) {if( L <= l && R >= r ) {sum2[rt] += ( r - l + 1 ) * add * add + 2 * add * sum1[rt]  ;sum1[rt] += ( r - l + 1 ) * add ;lazy[rt] += add ;return ;}if( lazy[rt] ) {pushdown( rt , r - l + 1 ) ;}int m = ( l + r ) >> 1 ;if( L <= m ) {update( lson , L , R , add ) ;}if( R > m ) {update( rson , L , R , add ) ;}pushup( rt ) ;}long long query1( int l , int r , int rt , int L , int R ) {if( L <= l && R >= r ) {return sum1[rt] ;}  if( lazy[rt] ) pushdown( rt , r - l + 1 ) ;int m = ( l + r ) >> 1 ;long long ans = 0 ;if( L <= m ) {ans += query1( lson , L , R ) ;}if( R > m ) {ans += query1( rson , L , R ) ;}return ans ;}long long query2( int l , int r , int rt , int L , int R ) {if( L <= l && R >= r ) {return sum2[rt] ;}  if( lazy[rt] ) pushdown( rt , r - l + 1 ) ;int m = ( l + r ) >> 1 ;long long ans = 0 ;if( L <= m ) {ans += query2( lson , L , R ) ;}if( R > m ) {ans += query2( rson , L , R ) ;}return ans ;}int main(){int n , m ;long long s1 , s2 ;double ave , ans ;while( scanf( "%d%d" , &n , &m )!= EOF ) {build( 1 , n , 1 ) ;while( m -- ) {int op ;scanf( "%d" , &op ) ;if( op == 1 ) {int a , b , c ;scanf( "%d%d%d" , &a , &b , &c ) ;a ++ ; b ++ ; update( 1 , n , 1 , a , b , c ) ;}else if( op == 2 ) {int a , b ;scanf( "%d%d" , &a , &b ) ; a ++ ; b ++ ;s1 = query1( 1 , n , 1 , a , b ) ;s2 = query2( 1 , n , 1 , a , b ) ;ave = s1 * 1.0 / ( b - a + 1 ) ;ans = ( s2 - 2 * ave * s1 + ave * ave * ( b - a + 1 ) ) / ( b - a + 1 ) ;printf( "%lld\n" , (long long)ans ) ;}}}return 0 ;}

J题 Set Time

http://acm.nbut.edu.cn/Problem/view.xhtml?id=1561

题意 : 就是计算s秒时候是处在第几天和第几个月第几年

思路: 嘛...我可耻的拖了zju的模板...



#include <stdio.h>#include <string.h>#include <algorithm>#include <map>#include <math.h>using namespace std ;int days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};class Date {public:   //判闰年   inline static bool isLeap(int year) {      return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;   }   int year, month, day;   //判合法性   inline bool isLegal() const {      if (month <= 0 || month > 12) {         return false;      } if (month == 2) {         return day > 0 && day <= 28 + isLeap(year);      }      return day > 0 && day <= days[month - 1];   }   //比较日期大小   inline int compareTo(const Date & other) const {      if (year != other.year) {         return year - other.year;      }      if (month != other.month) {         return month - other.month;      }      return day - other.day;   }   //返回指定日期是星期几  0 (Sunday) ... 6 (Saturday)   inline int toWeekday() const {      int tm = month >= 3 ? (month - 2) : (month + 10);      int ty = month >= 3 ? year : (year - 1);      return (ty + ty / 4 - ty / 100 + ty / 400 + (int)(2.6 * tm - 0.2) + day) % 7;   }   //日期转天数偏移   inline int toInt() const {      int ret = year * 365 + (year - 1) / 4 - (year - 1) / 100 + (year - 1) / 400;      days[1] += isLeap(year);      for (int i = 0; i < month - 1; ret += days[i++]);      days[1] = 28;      return ret + day;   }   //天数偏移转日期   inline void fromInt(int a) {      year = a / 146097 * 400;      for (a %= 146097; a >= 365 + isLeap(year); a -= 365 + isLeap(year), year++);      days[1] += isLeap(year);      for (month = 1; a >= days[month - 1]; a -= days[month - 1], month++);      days[1] = 28;      day = a + 1;   }}start,finish;int main(){int n ;start.year = 1970 , start.month = 1 , start.day = 1 ;int start_shift = start.toInt () ;while( scanf( "%d" , &n ) != EOF ) {int day = n / ( 60 * 60 * 24 ) ;int finish_shift = start_shift + day ;finish.fromInt( finish_shift ) ;printf( "year: %d\n" , finish.year - 1970 + 1 ) ;printf( "month: %d\n" , ( finish.year - 1970 ) * 12 + finish.month ) ;printf( "day: %d\n" , day + 1 ) ;}return 0 ;}

K题 Brick Game 博弈

http://acm.nbut.edu.cn/Problem/view.xhtml?id=1562

题意: ... 题意在比赛两天后...才知道真正的题意 ... T。T读错了两遍...

题意是给你n堆石子,你有两种操作:

1. 可以选择1堆石头减少石头的个数

2.可以选择1堆石头个数为0的石堆,删除后面所有的石堆( 注意是删除,不是清零,清零石堆还是存在的 ,删除是石堆就不存在了 )

谁把最后一个石堆删掉,谁就赢。

举个例子 : 0000 这个应该是先手胜 ...


思路 : 直接根据PN状态记忆化搜索即可... 数据量不大


#include <stdio.h>#include <string.h>#include <algorithm>using namespace std;int dp[1000005][6] ;int ten[9] ;int solve( int n , int len ) {if( dp[n][len-1] != -1 ) return dp[n][len-1] ;if( n < ten[len] ) {return dp[n][len] = 1 ;}int t = n ;for( int i = 1 ; i <= len ; i ++ ) {int tmp = t % 10 ; t /= 10 ;if( tmp > 0 ) {for( int j = 1 ; j <= tmp ; j ++ ) {if( solve( n - j * ten[i] , len ) == 0 ) {return dp[n][len-1] = 1 ;}}}else{if( solve( t , len - i ) == 0 ) {return dp[n][len-1] = 1 ;}}}return dp[n][len-1] = 0 ;}int main(){ten[1] = 1 ;for( int i = 2 ; i <= 7 ; i ++ ) ten[i] = ten[i-1] * 10 ;memset( dp , -1 , sizeof(dp) ) ;int n ;char str[15] ;while( scanf( "%s" , &str ) != EOF ) {int len = strlen( str ) ;sscanf( str , "%d" , &n ) ;if( n == 0 ) {puts( "Hahaha" ) ; continue ;}puts( solve( n , len ) ? "Hahaha":"Papapa" ) ;} return 0 ;}


0 0
原创粉丝点击