atcoder grand contest 024

A - Fairness


给定 A,B,C A , B , C ,进行以下操作 K K 次:

  • 每个数都同时变为其他两个数的和

K K 次操作后 AB A − B 的值,如果答案大于 1018 10 18 ,输出'unfair'

A,B,C109K1018 A , B , C ≤ 10 9 , K ≤ 10 18


设每次操作后每个数的值等于 αx+βy α x + β y ,其中 x x 为最开始时自己的值, y y 为最开始时其他两个数的和, 我们要求的就是
αA+βBαBβA α A + β B − α B − β A

我们发现对于3个数,每次操作后的 α α β β 都是相等的。

找一找规律,我们发现偶数次操作后, αβ=1 α − β = 1 ,奇数次操作后, βα=1 β − α = 1

所以我们可以 O(1) O ( 1 ) 求出答案。而且我们发现答案并不会超过 1018 10 18

code:

#include 
using namespace std;

int a, b, c; long long k;

int main()
{
    scanf( "%d%d%d%lld", &a, &b, &c, &k );

    if( k % 2 == 0 ) 
        printf( "%d\n", a - b );
    else printf( "%d\n", b - a );

    return 0;
}

B - Backfront


给定一个序列 P P ,它是 1 1 N N 的排列,你要通过做以下操作来将该序列排序成递增的:

  • 选择一个序列中的元素,将它放到序列最前或者最后面

求需要的最少的操作次数

N2×105 N ≤ 2 × 10 5


可以发现,将 N N 1 1 依次放到序列最前方或者将 1 1 N N 依次放到序列最后方是一定可以将序列排好序的

这个操作不允许我们将一个数放到两个数中间,如果我们存在 x,x+1,x+2 x , x + 1 , x + 2 ,其中 x+2 x + 2 x x x+1 x + 1 中间,那我们就只能将 x+2 x + 2 N N 1 1 x+1 x + 1 暴力重构

所以我们只需要找出序列中最长的连续上升子序列,这部分是不需要重构的,然后将不属于这个子序列的数都暴力重构就可以了

code:

#include 
using namespace std;
const int N = 300000;

int n, a[N], f[N], ans = 0;

inline void ck_max( int &a, int b ) { if( a < b ) a = b; }

int main()
{
    scanf( "%d", &n );

    for( int i = 1; i <= n; i ++ )
        scanf( "%d", &a[i] );

    memset( f, 0, sizeof( f ) );
    for( int i = 1; i <= n; i ++ )
        f[ a[i] ] = f[ a[i] - 1 ] + 1, ck_max( ans, f[ a[i] ] );

    printf( "%d\n", n - ans );

    return 0;
}

C - Sequence Growing Easy


有一个长度为 N N 的序列 X X ,其中每个元素都是 0 0
给你一个长度为 N N 的序列 A A ,你可以对 X X 进行任意次以下操作:

  • 选定一个 i i ,使 1iN1 1 ≤ i ≤ N − 1 ,令 Xi+1=Xi+1 X i + 1 = X i + 1

输出将 X X 变成 A A 的最少操作数,如果不能的话,输出 1 − 1

N2×1050Ai109 N ≤ 2 × 10 5 , 0 ≤ A i ≤ 10 9


如果我们要将序列中某一个数变成 N N 的话,那我们必定会在某一时刻将序列变成以下这种样子

...,1,2,3,...,N1,N,... . . . , 1 , 2 , 3 , . . . , N − 1 , N , . . .

然而我们是没有办法将一个数减少的,所以如果 AiAi12 A i − A i − 1 ≥ 2 ,那么一定是不可以的(我们设 A0=1 A 0 = − 1

对于序列中的一个数 i i ,如果 AiAi1=1 A i − A i − 1 = 1 ,我们只需要在做好 Ai1 A i − 1 的基础上进行一次操作就可以做好 Ai A i ,否则我们需要暴力的进行 Ai A i 次操作

至此我们扫一遍即可解决问题

code:

#include 
using namespace std;
const int N = 300000;

int n, a[N]; long long ans = 0;

int main()
{
    scanf( "%d", &n );

    for( int i = 1; i <= n; i ++ )
        scanf( "%d", &a[i] );

    if( a[1] != 0 ) {
        printf( "-1\n" ); return 0;
    }

    for( int i = 2; i <= n; i ++ )
    {
        if( a[i] - a[i-1] >= 2 ) {
            printf( "-1\n" ); return 0;
        }

        if( a[i] - a[i-1] == 1 ) 
            ans ++;
        else ans += a[i];
    }

    printf( "%lld\n", ans );

    return 0;
}

D - Isomorphism Freak


对于一颗树 G G 的顶点染色方案,如果同种颜色的每个节点作为根时形成的有根树都是同构的,那我们称这种染色方案为 good coloring,将这种染色方案的颜色总数称为这棵树的 colofulness

给定一颗 N N 个结点的树,你可以进行任意次以下操作来重构它:

  • 新建一个结点,将它与树上的任意一个结点用一条边连接起来

找出重构后树的最小 colorfulness,在 colorfulness 最小的情况下,找出最小的叶子数量

N200 N ≤ 200


我们发现增加新点并不能使树的 colorfulness 减少

我们找出树的直径,设其长度为 D D ,我们可以发现该树的 colorfulness 为 D2 ⌊ D 2 ⌋

所以第一个任务就完成了

每棵树都有一个或两个中心,当 D D 为偶数时,中间的两个点就是中心,当 D D 为奇数时,中间的一个点可以是中心,那个点与其相邻的任意一个点也可以组成两个点的中心。

我们发现对于一个确定的中心,这棵树的叶子数量是一定的

我们只需要暴力枚举中心,找出最小的叶子数量即可

code:
(这份代码 WA 了一个点…并不知道原因,求大神解答)

#include 

const int N = 200;
const int INF = 1<<30;

inline int read()
{
    char c = getchar(); int f = 1, x = 0;
    while( c < '0' || c>'9' ) { if(c == '-') f = -1;  c = getchar(); }
    while( c >= '0'&&c<='9' ) { x = x * 10 + c - '0'; c = getchar(); }
    return f * x;
}

int tot = 0, front[N];

struct tEdge {
    int v, next;
} e[N<<1];

inline void add_edge( int u, int v ) {
    e[++tot] = { v, front[u] }; front[u] = tot;
    e[++tot] = { u, front[v] }; front[v] = tot; 
}

int n, m = INF, max_dep[N];

inline int find_max_dep( int u, int fa )
{
    int ret = 0;

    for( int i = front[u]; i; i = e[i].next ) 
    {
        int v = e[i].v;
        if( v == fa ) continue;

        ret = std::max( ret, find_max_dep( v, u ) );
    }

    return ret + 1;
}

int sum[N];

inline int find_leaves( int u, int fa, int dep )
{
    int now_sum = 0;

    for( int i = front[u]; i; i = e[i].next )
    {
        int v = e[i].v;
        if( v == fa ) continue;

        find_leaves( v, u, dep + 1 );
        now_sum ++;
    } 

    sum[dep] = std::max( sum[dep], now_sum );
}

inline int calc_ans( int u, int v )
{
    int ret = 0;
    if( !v )
    {
        for( int i = front[u]; i; i = e[i].next )
            find_leaves( e[i].v, u, 1 ), ret ++;

        for( int i = 1; sum[i]; i ++ )
            ret *= sum[i], sum[i] = 0;

        return ret == 0 ? 1 : ret;
    }

    ret = 2;
    find_leaves( u, v, 1 );
    find_leaves( v, u, 1 );

    for( int i = 1; sum[i]; i ++)
        ret *= sum[i], sum[i] = 0;
    return ret;
}

int main()
{
    memset( max_dep, 0, sizeof( max_dep ) ); 

    n = read();
    for( int i = 1; i < n; i ++ )
        add_edge( read(), read() );

    int x = 0, y = 0;
    for( int i = 1; i <= n; i ++ )
        max_dep[i] = find_max_dep( i, 0 ),
        m = std::min( m, max_dep[i] );

    for( int i = 1; i <= n; i ++ )
        if( max_dep[i] == m ) 
            y = x ? i : 0, x = x ? x : i;

    int ans;
    if( !y )
    {
        ans = calc_ans( x, 0 );
        for( int i = front[x]; i; i = e[i].next )
            ans = std::min( ans, calc_ans( x, e[i].v ) );
    }
    else ans = calc_ans( x, y );

    printf( "%d %d\n", m - ( !!y ), ans );

    return 0;
}

E - Sequence Growing Hard


找出符合以下条件的序列组 (A0,A1,...,AN) ( A 0 , A 1 , . . . , A N ) 的数量,模 M M

  • 序列组中每个序列中的每个数都是一个 1 1 K K 之间的整数(包括 1 1 K K
  • 对于每个满足 1iN 1 ≤ i ≤ N i i Ai1 A i − 1 Ai A i 的一个子序列
  • 对于每个满足 1iN 1 ≤ i ≤ N i i Ai A i 的字典序比 Ai1 A i − 1
  • 序列 Ai A i 的长度为 i i

1N,K3002M109 1 ≤ N , K ≤ 300 , 2 ≤ M ≤ 10 9


每个序列 Ai A i 就相当于在上一个序列 Ai1 A i − 1 的基础上插入了一个数字,由于要求字典序递增,插入的数字必须大于等于原来在该位置上的数字。然而如果相等的话,我们其实就相当于在原来位置的后面一位插入了一个比它大的数字,所以我们只需要考虑插入的数字比原来大的情况。

4,3,2,1 4 , 3 , 2 , 1
4,3,3,2,1 4 , 3 , 3 , 2 , 1
3 3 前面插入的 3 3 可以视为在 2 2 前面插入了一个 3 3

我们将这个插入的过程转化成一棵树。树上的每个节点包含两个值, id i d label l a b e l ,分别代表了该结点是第几次插入的和结点的值,而一条边就相当于儿子插入在了父亲的左边
例如, A0 A 0 就相当于一个 (0,0) ( 0 , 0 ) 的结点, {1} { 1 } 就相当于 (0,0)(1,1) ( 0 , 0 ) − ( 1 , 1 )

那么我们要求的就是满足条件的树的个数

我们设 fi,j f i , j 代表 i i 个结点的树,根节点值为 j j 的树的数量。我们发现 id=1 i d = 1 一定是 id=0 i d = 0 的儿子,那么我们有:

fi,j=x=1i1j>jkfx,j×fix,j×Cx1i2 f i , j = ∑ x = 1 i − 1 ∑ j ′ > j k f x , j ′ × f i − x , j × C i − 2 x − 1

其中 x x 代表子树内有多少个结点, j j ′ 是子树根的结点的值, Cx1i2 C i − 2 x − 1 枚举了两个块内儿子的次序

=x=1i1fix,j×Cx1i2×(j>jkfx,j) = ∑ x = 1 i − 1 f i − x , j × C i − 2 x − 1 × ( ∑ j ′ > j k f x , j ′ )

我们求一下前缀和,设

S(i,j)=k=1ifi,k S ( i , j ) = ∑ k = 1 i f i , k

fi,j=x=1i1fix×Cx1i2×(S(x,k)S(x,j)) f i , j = ∑ x = 1 i − 1 f i − x × C i − 2 x − 1 × ( S ( x , k ) − S ( x , j ) )

这样我们就可以在 O(KN2) O ( K N 2 ) 内解决问题

code:

#include 

typedef long long ll;

const int N = 500;


int n, k, mod;

inline ll add( ll a, ll b ) { ll ret = a + b; if( ret >= mod ) ret -= mod; return ret; }

ll fac[N], inv_f[N], f[N][N], s[N][N], c[N][N];

inline ll C( ll n, ll m ) { 
    return c[n][m];
}

int main()
{
    memset( f, 0, sizeof( f ) );
    memset( s, 0, sizeof( s ) );

    scanf( "%d%d%d", &n, &k, &mod );

    for( int i = 0; i <= n; i ++ )
    {
        c[i][0] = 1;
        for( int j = 1; j <= i; j ++ )
            c[i][j] = add( c[i-1][j-1], c[i-1][j] );
    }

    for( int i = 0; i <= k; i ++ ) 
        f[1][i] = 1; 
    for( int i = 0; i <= k; i ++ )
        s[1][i] = !i ? f[1][i] : s[1][i-1] + f[1][i];  

    for( int i = 2; i <= n + 1; i ++ )
    // i : 现在的结点个数
        for( int j = 0; j <= k; j ++ )
        {
        // j : 现在根节点的 label 值
            for( int l = 1; l < i; l ++ )
            // l : ID=1 子树含有多少个结点
                f[i][j] = add( f[i][j], C( i-2, l-1 ) * f[i-l][j] % mod * add( s[l][k], mod - s[l][j] ) % mod ),

//          printf( "%lld, %lld, %lld\n", C( i-2, l-1 ), f[i-l][j], add( s[l][k], mod - s[l][j] ) );

            s[i][j] = !j ? f[i][j] : add( s[i][j-1], f[i][j] );
//          printf( "i:%d, j:%d, f:%lld, s:%lld\n", i, j, f[i][j], s[i][j] );
        }

    printf( "%lld\n", f[n+1][0] );

    return 0;
}

你可能感兴趣的:(agc,atcoder)