ICPC2018 青岛站 有关题目题解

热身赛D题

题意:

第一行给n个数,第二行给m个数,m<=n,从n个数中取m个数和第二行的m个数匹配,匹配花费abs(ai-bj),求最小匹配花费。

思路:

建立三个堆,A堆存放第一行的数,B堆存放第二行中未被匹配过的数,C堆存放第二行中被匹配过的数。将n+m个数进行混合排序,从前向后扫描数组。

遇到第一行的数:

1.若B堆非空,则与B堆中花费最小的进行匹配

2.若B堆空的,则看C堆中是否存在匹配使得答案更优,若有取出匹配优化答案,同时将结果取反以及当前节点权值取反放入堆A中,表示若有其它第二行数想匹配这个A所需要花费的代价,类似费用流的反悔操作

3.其它情况,则将该数取反放入堆A

遇到第二行的数:

1.若A堆非空,则匹配A堆中情况最优的,同时将结果取反以及当前节点权值取反放入堆C中,表示若有其它第一行数想匹配这个B所需要花费的代价,类似费用流的反悔操作

2.若A堆空的,则将该数取反放入堆B

代码:

#include
using namespace  std;
typedef long long LL;
const int maxn = 200010;
struct node
{
    int val,type;
    friend bool operator<( const node&a , const node&b )
    {
        return a.val,greater >A,B,C;
        LL ans = 0;
        for( int i=1 ; i<=n+m ; i++ )
        {
            LL x = a[i].val;
            if( a[i].type==0 )
            {
                if ( !B.empty() )
                {
                    LL t = B.top();
                    B.pop();
                    ans += x+t;
                }
                else if ( !C.empty() )
                {
                    LL t = C.top();
                    if ( x+t<0 )
                    {
                        C.pop();
                        ans += x+t;
                        A.push(-x-t-x);
                    }
                    else A.push(-x);
                }
                else A.push(-x);
            }
            else
            {
                if( !A.empty() )
                {
                    LL t = A.top();
                    A.pop();
                    ans += x+t;
                    C.push(-x-t-x);
                }
                else B.push(-x);
            }
        }
        printf( "%lld\n" , ans );
    }
    return 0;
}

正赛C题

题意:

给两个01串A串和B串,在A串中可以进行区间翻转操作,可以使得一个区间内所有的0变成1,所有的1变成0,求恰好翻转两次使得A串变为B串的方案数。

思路:

根据两个01串连续不相同的段数

段数为0:答案n*(n+1)/2,即区间数

段数为1:答案n-1

段数为2:答案6

段数3以上:答案0

代码:

#include
using namespace  std;
typedef long long LL;
const int maxn = 1000010;
char s[maxn];
char t[maxn];
int main()
{
    int T; scanf( "%d" , &T );
    for( int cas=1 ; cas<=T ; cas++ )
    {
        int n; scanf( "%d" , &n );
        scanf( "%s" , s );
        scanf( "%s" , t );
        int cnt = 0;
        for( int i=0 ; i

正赛D题

题意:

有两个只知道长度的未知数A和B,和一个已知数C。C是由A,B通过如下计算而来:假设A为54,B为23,C = 5*2 + 5*3 + 4*2 + 4*3 = 1015812,+表示连接符。求字典序最小的A和B,若无解输出Impossible。

思路:

从1到9枚举A[1],得知A[1]之后就可以确定B,确定B以后再反过头来确定是否存在A[2]...A[n]的解

代码:

#include
using namespace  std;
typedef long long LL;
const int maxn = 200010;
char s[maxn]; int n,m,a[maxn],b[maxn],len;
bool ok( int pos )
{
    for( int i=2 ; i<=n ; i++ )
    {
        if ( pos==len ) return false;
        int t = s[pos++]-'0';
        if ( t%b[1]==0 ) a[i] = t/b[1];
        else if ( t>b[1] ) return false;
        else
        {
            if ( pos==len ) return false;
            t = t*10+s[pos++]-'0';
            if ( t%b[1]==0 ) a[i] = t/b[1];
            else return false;
        }
        for( int j=2 ; j<=m ; j++ )
        {
            if( pos==len ) return false;
            t = s[pos++]-'0';
            if ( b[j]==0&&t!=0 ) return false;
            if ( b[j]==0&&t==0 ) continue;
            if ( t%b[j]==0 )
            {
                if ( t/b[j]!=a[i] ) return false;
            }
            else
            {
                if ( pos==len ) return false;
                t = t*10+s[pos++]-'0';
                if ( t%b[j]!=0||t%b[j]==0&&t/b[j]!=a[i] ) return false;
            }
        }
    }
    return pos==len;
}
int main()
{
    int T; scanf( "%d" , &T );
    for( int cas=1 ; cas<=T ; cas++ )
    {
        scanf( "%d%d%s" , &n , &m , s ); len=strlen(s);
        for( a[1]=1 ; a[1]<=9 ; a[1]++ )
        {
            int  pos = 0,idx = 0;
            while(posa[1] ) break;
                else
                {
                    if ( pos==len ) break;
                    t = t*10+s[pos++]-'0';
                    if ( t%a[1]==0 ) b[++idx] = t/a[1];
                    else break;
                }
            }
            if(idx

正赛E题

题意:

有n株植物,每株植物有个成长值ai,表示被浇一次水后植物的成长高度,有个浇水机器人从0出发,每秒必须向左或向右移动一步,停在植物上时会给植物浇水,植物初始高度为0,机器人走m步以后,求n株植物中的最低高度

思路:

二分高度,获得每株植物的浇水次数,然后使用贪心检验是否可行,注意检验过程可能爆LL

代码:

#include
using namespace  std;
typedef long long LL;
LL Min( LL a , LL b ){ return ab?a:b; }
const int maxn = 100010;
int n,a[maxn]; LL m,b[maxn];
bool check()
{
    LL cnt = 0;
    for( int i=1 ; i<=n ; i++ )
    {
        if ( i!=n||b[i]!=0 ) cnt++,b[i] = Max( 0 , b[i]-1 );
        cnt += b[i]*2; b[i+1] = Max( 0 , b[i+1]-b[i] );
        if ( cnt>m ) return false;
    }
    return cnt<=m;
}
int main()
{
    int T; scanf( "%d" , &T );
    for( int cas=1 ; cas<=T ; cas++ )
    {
        scanf( "%d%lld" , &n , &m );
        for ( int i=1 ; i<=n ; i++ )
            scanf( "%d" , &a[i] );
        LL l=1,r=200000000000000000LL;
        while( l<=r )
        {
            LL mid = (l+r)>>1;
            for( int i=1 ; i<=n ; i++ )
                if ( mid%a[i]==0 ) b[i] = mid/a[i];
                else b[i] = mid/a[i]+1;
            if( check() ) l = mid+1;
            else r = mid-1;
        }
        LL ans;
        if ( r%a[1]==0 ) ans = r;
        else ans = Max( r , (r/a[1]+1)*a[1] );
        for( int i=2 ; i<=n ; i++ )
        {
            if ( r%a[i]==0 ) ans = Min( ans , r );
            else ans = Min( ans , (r/a[i]+1)*a[i] );
        }
        printf( "%lld\n" , ans );
    }
    return 0;
}

正赛F题

题意:

国王组织骑士们两两比武,骑士编号1到n,比武有如下规则:1.骑士不会和已经比过武的对手比武。2.如果在当前局a与b比武c与d比武,那么如果下局a与c比武则b必须与d比武。现在有n个骑士报名比武,国王想要进行k场比武。每次比武有一个序列A,A[i]表示编号为i的骑士和编号为A[i]的骑士比武。在符合规则的情况下,若能进行k次比武,则输出字典序最小解,若不能则输出Impossible

思路:

n个骑士最多可以进行lowbit(n)-1场比武,比如:

四位骑士最多可以进行三场比赛

2 1 4 3

3 4 1 2

4 3 2 1

六位骑士最多可以进行1场比赛

2 1 4 3 6 5

其它排序都不满足比武规则2

观察六位骑士的比武情况,可以看成如下情况:2+2*(1-1)  1+2*(1-1) 2+2*(2-1) 1+2*(2-1) 2+2*(3-1) 1+2*(3-1),可以将序列分成n/lowbit(n)等分,每一份的对应位置上都是前一份对应位置上的数+lowbit(n)

对于四位骑士的比武情况,将1 2 3 4这一行添加在第一行上

1 2 3 4

2 1 4 3

3 4 1 2

4 3 2 1

观察发现

1 2

2 1

这个2*2的方阵里头可以看成

1 1+1

1+1 1 

同样的4*4的方阵块用看成

1 2 1+2 2+2

2 1 2+2 1+2

1+2 2+2 1 2

2+2 1+2 2 1

总结:n*n的矩阵往2n*2n的矩阵推的时候,相当于n*n矩阵中每一项+n,向下和向右复制一份,将矩阵本身向右下复制一份

比如:由4*4的矩阵推得8*8的矩阵

1 2 3 4 5 6 7 8

2 1 4 3 6 5 8 7

3 4 1 2 7 8 5 6

4 3 2 1 8 7 6 5

5 6 7 8 1 2 3 4

6 5 8 7 2 1 4 3

7 8 5 6 3 4 1 2

8 7 6 5 4 3 2 1

代码:

#include
using namespace  std;
const int maxn = 610;
int a[maxn][maxn];
int f(int n)
{
    int res = 1;
    while( n%(res*2)==0 ) res*=2;
    return res;
}
int main()
{
    a[1][1] = 1;
    for( int i=0 ; i<9 ; i++ )
    {
        int k=(1<=x ) printf( "Impossible\n" );
        else
        {
            for( int i=2 ; i<=m+1 ; i++ )
            {
                int y = n/x;
                for( int p=1 ; p<=y ; p++ )
                    for( int q=1 ; q<=x ; q++ )
                    {
                        if ( p!=1||q!=1 )  printf( " " );
                        printf( "%d" , a[i][q]+(p-1)*x );
                    }
                printf( "\n" );
            }
        }
    }
    return 0;
}

正赛J题

题意:

有n本书从第1本书开始买,对于每本书能买则买,求恰好买m本书的最大携带钱数,如果携带钱数没有上限输出Richman,如果不存在购买m本书的情况输出Impossible

思路:

注意有书的价格为0,这些书相当白送,处理掉。携带钱数最大化那肯定要使花钱数最大化,最优选择自然是去买那些最贵的书,但由于强制能买就买,所以买的书必然是前m本书,此外你还可以带上剩下书中最便宜的那本价格-1

代码:

#include
using namespace  std;
typedef long long LL;
const int maxn = 100010;
int Min( int a , int b ){ return a

 

你可能感兴趣的:(套题)