2017.11.9机房小测-Exgcd+特判/树形DP/模拟

来源:互联网 发布:数据库原理 编辑:程序博客网 时间:2024/05/05 15:42

致出题人:此篇blog中所提到的题目如果侵犯了您的版权,请与me联系,me将及时删除。


说在前面

今天考的题好像也比较老的了,是一套2015年NOIP的模拟题=w=
总的来说整套题要考高分还是很容易的,AK嘛…还是别想了(第三题是Codeforces Round#168 div.1 E:Mirror Room一道上古老题,貌似十分难,std两百七十行)。第一题是简单exgcd,不过特判很多啦,考场上没能把特判写全,丢掉了20分。第二题是树形DP,类似模型的题目挺多的,在考场上me就把这道题写出来了。最后是第三题,数据很友好,有60分可以直接模拟,剩下的四个点还可以特判骗点分,这道题me拿到了70分。

今天的考试呢,三道题考的方向都是很明显的,所以在审题上没花多少时间。不过第二题的DP很坑呐,做法是多叉转二叉的DP,一般是倒着for的。然而这个DP数组会被覆盖,所以不得不使用一个temp数组存原来的值,me用printf查错大法找了一个多小时才发现这个问题。

其他的好像就没有什么想要写的了。
两天后就是NOIP了,从来没有这么紧张过啊,毕竟能否留下来继续往后走就是这一次了。准考证已经拿到了,不过丑的一比= =,me所在的考场还有好多同校大佬!自己也不能示弱啊!


T1

这里写图片描述


解法

一道很简单的exgcd对吧!
不对不对不对…会有很多特殊情况的
因为a,b,c有正有负,还可能为0,因此有很多特殊情况需要判定。
具体判定的内容有:

  • 如果a,b都为0
    • c也是0,则有无数多组解
    • c不是0,则无解
  • 如果a,b中有一个数字为0(假设是b为0)
    • 如果c为0,无解
    • 如果c不是0
      • 如果c是a的倍数,无数多组解
      • 如果c不是a的倍数,无解
  • 如果a,b都不是0
    • 如果a,b同号,使用Exgcd求解
    • 如果a,b异号(假设d=gcd(a,b))
      • 如果c为0,无数多组解
      • 如果c不为0,c是d的倍数则无数多组解,否则无解

下面是自带大常数的代码

#include <cstdio>#include <cstring>#include <algorithm>using namespace std ;int T ;long long exgcd( int a , int b , long long &x , long long &y ){    if( !b ){        x = 1 , y = 0 ;        return a ;    } else {        long long xx , yy , d = exgcd( b , a%b , xx , yy ) ;        x = yy , y = xx - a / b * yy ;        return d ;    }}void solve( int a , int b , int c ){    if( !a || !b ){        if( !a ) swap( a , b ) ;        if( !a ){            if( c ) printf( "0\n" ) ;            else    puts("ZenMeZheMeDuo") ;        } else {            if( c*a <=0 || c%a != 0 )printf( "0\n" ) ;            else                puts("ZenMeZheMeDuo") ;        }        return ;    }    if( !c ){        if( a > 0 && b > 0 ) printf( "0\n" ) ;        else if( a < 0 && b < 0 ) printf( "0\n" ) ;        else puts("ZenMeZheMeDuo") ;        return ;    }    long long x , y , d = exgcd( a , b , x , y ) ;    if( c%d ){        puts("0") ;        return ;    }    if( ( a > 0 && b < 0 ) || ( a < 0 && b > 0 ) ){        puts("ZenMeZheMeDuo") ;        return ;    }    x *= c / d , y *= c / d ;    int xd = b / d , yd = a / d , tmpx , tmpy , rt ;    tmpx = ( x % xd + xd ) %xd ;    if( tmpx == 0 ) tmpx += xd ;    tmpy = ( c - tmpx * a ) / b ;    y = ( y % yd + yd ) %yd ;    if( !y ) y += yd ;    rt = ( tmpy - y ) / yd + 1 ;    if( rt <= 0 ) puts("0") ;    else if( rt <= 65535 ) printf( "%d\n" , rt ) ;    else puts("ZenMeZheMeDuo") ;}int main(){    scanf( "%d" , &T ) ;    for( int i = 1 , a , b , c ; i <= T ; i ++ ){        scanf( "%d%d%d" , &a , &b , &c ) ;        solve( a , b , c ) ;    }} 


T2

这里写图片描述

解法

对每条边我们单独计算它对答案的贡献。
因为每个白点和每个白点之间会有贡献,黑点和黑点之间也有贡献,那么对于一条连接u,v的边来说,它的贡献就是:
这里写图片描述 (v子树中黑点的数量 * 除v子树外黑点的数量+v子树中白点的数量 * 除v子树外白点的数量)* 边权。然后就是DP了。

下面是自带大常数的代码

#include <cstdio>#include <cstring>#include <algorithm>using namespace std ;long long dp[2005][2005] , tmp[2005] ;int N , M , tp , head[2005] , siz[2005] ;struct Path{    int pre , to , len ;}p[4005] ;void In( int t1 , int t2 , int t3 ){    p[++tp].pre = head[t1] ;    p[ head[t1] = tp ].to = t2 ;    p[tp].len = t3 ;}inline long long cal( int siz , int bk , int len ){    return 1LL * ( bk*(M-bk) + (siz-bk)*(N-M-(siz-bk)) ) * len ;}void dfs( int u , int f , int flen ){    siz[u] = 1 ;    for( int i = head[u] ; i ; i = p[i].pre ){        int v = p[i].to , len = p[i].len ;        if( v == f ) continue ;        dfs( v , u , len ) ;        for( int k = 0 ; k <= min( M , siz[u] ) ; k ++ )            tmp[k] = dp[u][k] ;        for( int j = 0 ; j <= siz[v] && j <= M ; j ++ )            for( int k = 0 ; k + j <= M && k <= siz[u] ; k ++ )                dp[u][k+j] = max( dp[u][k+j] , tmp[k] + dp[v][j] ) ;        siz[u] += siz[v] ;    }    for( int i = 0 ; i <= min( M , siz[u] ) ; i ++ )        dp[u][i] += cal( siz[u] , i , flen ) ;}void solve(){    dfs( 1 , 0 , 0 ) ;    printf( "%lld" , dp[1][M] ) ;}int main(){    scanf( "%d%d" , &N , &M ) ;    for( int i = 1 , u , v , len ; i < N ; i ++ ){        scanf( "%d%d%d" , &u , &v , &len ) ;        In( u , v , len ) ; In( v , u , len ) ;    }    solve() ;}/*5 21 2 11 3 22 4 22 5 1*/

T3

这里写图片描述这里写图片描述

解法

直接模拟
官方题解见:Codeforces Round #168 Editorial

274E - Mirror Room
Author: havaliza
The blocked cells can make lots of complicated patterns. So it’s obvious that the solution in includes simulating the path the laser beam goes. But the dimensions of the gird are large and the beam might travel a long path before entering a loop. So naïve simulation will surely time out (See note!).
It’s kind of obvious that the beam finally comes back to its initial position and direction. Here were going to prove that the maximum number of times the beam might reflect until it reaches its first state is O(n + m + k). Consider an empty grid, It has O(n + m) maximal empty diagonal segments. When we block a cell, the two diagonal segments which pass this cell split. So the number of maximal empty diagonal segments increases by two. There for there are O(n + m + k) of these segments. Also If you look at the behavior of the beam it passes some of the segments one after another. So if you simulate the beam, it reflects O(n + m + k) times. Instead of naïve simulation we can find the next position the beam reflects.
Now we’re going to prove that no cell will be visited twice. A cell gets visited twice in the cycle if we pass it in both NE-SW direction and NW-SE direction. Consider the grid colored in black and white like a chessboard. There are two types of diagonal segments the NE-SW ones and NW-SE ones (property 1). At each reflection we alternate between these two. Also there are two types of segments in another way, black segments and white segments (property 2). As you can see each time one of the properties changes the other one also changes. As a result we’ll never pass a black cell in both directions, and the same is for a white cell.
So this problem can be solved with simulation in O((n + m + k)lgk).
You’d like to read Petr’s solution for a clean implementation of this approach. 3162462
Note: The random tests I had generated before the contest were weak and I didn’t notice that naïve simulation solutions would pass the test. Now the tests are more powerful. :)

自带大常数的代码

#include <cstdio>#include <cstring>#include <algorithm>using namespace std ;bool vis[1001][1001][4] , acc[1001][1001] ; bool ban[1003][1003] ;int N , M , K , x , y , t , ans , mov[4][2] = {{-1,1},{-1,-1},{1,1},{1,-1}};void fix( int &nx , int &ny , int &nt ){    if( !ban[nx][ny] ) return ;    else {        int tx = -mov[nt][0] , ty = -mov[nt][1] ;        if( ban[nx][ny] && !ban[nx][ny+ty] && !ban[nx+tx][ny] ){            nx += tx , ny += ty ;            nt = 3 - nt ;            return ;        }        if( ban[nx][ny] && ban[nx][ny+ty] && !ban[nx+tx][ny] ){            nx += tx ;            if( nt == 0 ) nt = 2 ;            else if( nt == 1 ) nt = 3 ;            else if( nt == 2 ) nt = 0 ;            else nt = 1 ;            return ;        }        if( ban[nx][ny] && !ban[nx][ny+ty] && ban[nx+tx][ny] ){            ny += ty ;            if( nt == 0 ) nt = 1 ;            else if( nt == 1 ) nt = 0 ;            else if( nt == 2 ) nt = 3 ;            else nt = 2 ;            return ;        }        if( ban[nx][ny] && ban[nx][ny+ty] && ban[nx+tx][ny] ){            nx += tx , ny += ty ;            nt = 3 - nt ;            return ;        }    }}void solve(){    while( !vis[x][y][t] ){        vis[x][y][t] = true ;        if( !acc[x][y] ){            ans ++ ;            acc[x][y] = true ;        }        int nx , ny , nt ;        switch( t ){            case 0: nx = x - 1 , ny = y + 1 , nt = t ; break ;            case 1: nx = x - 1 , ny = y - 1 , nt = t ; break ;            case 2: nx = x + 1 , ny = y + 1 , nt = t ; break ;            case 3: nx = x + 1 , ny = y - 1 , nt = t ; break ;        }        fix( nx , ny , nt ) ;        x = nx , y = ny , t = nt ;        //printf( "%d %d %d\n" , nx , ny , nt ) ; getchar() ;    }    printf( "%d" , ans ) ;}int main(){    char tmp[10] ;    scanf( "%d%d%d" , &N , &M , &K ) ;    if( N * M > 1000*1000 ){        if( N != M ) printf( "%lld" , 1LL * N * M ) ;        else{            for( int i = 1 ; i <= K ; i ++ )                scanf( "%*d%*d" ) ;            scanf( "%d%d" , &x , &y ) ;            scanf( "%s" , tmp ) ;            if( x == y || x + y == N + 1 )  printf( "%d" , N ) ;            else                            printf( "%d" , 2 * N ) ;        }        return 0 ;    }    for( int i = 1 , tmpx , tmpy ; i <= K ; i ++ ){        scanf( "%d%d" , &tmpx , &tmpy ) ;        ban[tmpx][tmpy] = true ;    }    for( int i = 0 ; i <= N + 1 ; i ++ )        ban[i][0] = ban[i][M+1] = true ;    for( int i = 0 ; i <= M + 1 ; i ++ )        ban[0][i] = ban[N+1][i] = true ;    scanf( "%d%d" , &x , &y ) ;    scanf( "%s" , tmp ) ;    if( tmp[0] == 'N' ){        if( tmp[1] == 'E' ) t = 0 ; //右上↗         else                t = 1 ; //左上↖     } else {        if( tmp[1] == 'E' ) t = 2 ; //右下↘         else                t = 3 ; //左下↙     }    solve() ;}/*7 5 32 33 34 32 1 SE*/
原创粉丝点击