训练赛---分球

分球 (divide)

来源

???不可说

题面

这里写图片描述

数据范围

30%:8  
20%:aibn:  
b1=b2==bnb1=b2,bi=2bi1(i3)   
100%:1n100001m<231   

说明

原题的评分标准:
1.打出的方案超过1000000步的得0分;
2.输出格式错误的得0分;
3.某组数据可行性判断错误的得0分;
4.每组数据可行性判断均正确,但方案错误的得4分;
5.每组数据可行性判断正确,方案也正确的得10分.

此题理论上有spj,实际上并没有,至少我做这道题的时候是没有spj的。

题解

原题解分析

原出题人题解非常之diao!
原文写道:

“迭代加深搜索or广度优先搜搜,期望得分0分!”
然而这个0分算法也不是很好写.虽然保证了步数不超过8步,但数据规模仍然是10000的。

双向宽搜,期望得分30分
这个我觉得不好写。

“针对特殊数据: b1=b2=b3==bn  如果 n=2k  那么可行,否则不可行。至于方案,每次把相同的两个数合并起来就行。期望得分0分”.
这个算法是对的,但是出题人坑爹.20%的特殊数据具有两种特性之一。但是每个测试点是多组数据,每组数据可能具有不同的特性,因此只针对一种特性要wa。

“针对两种特殊数据,结合双向宽搜,期望得分50分”
写得这么复杂,结果只有50分!

正解:算了,这个我来说吧,毕竟考场上我A了这道题,而且我敢说标程是wa的。

正解

我们知道,如果a[u],a[v]的最大公约数是d,那么进行了一波操作之后,a[u],a[v]的公约数变为d或者d/2,这就意味着最大公约数每次操作之后要么不变,要么减半;
所以通过合法操作最终得到的所有非0的a[i]最大公约数是 m2k
设我们求出的非零a[i]最大公约数为d,令t=m/d.如果(t&-t)!=t,那么肯定输出no.
如果(t&-t)==t,那么是否一定可行呢?这个是可行的,因为我们可以构造出一组可行的方案将这些a[i]还原成m.
先将所有的a[i]除以最大公约数d.
将满足a[i]>>0&1的数两两进行逆操作;
再将满足a[i]>>1&1的数两两进行逆操作;
再将满足a[i]>>2&1的数两两进行逆操作;
……
……
最后将满足a[i]>>31&1的数两两进行逆操作.
为什么是对的?因为每一轮操作之后,都会抹掉当前位的1.最终就会化为 2k .

但是逆操作有一个细节问题:操作是有序的,我们应该按何种顺序?
不妨设u < v.
当a[u] < a[v]时,a[v]-=a[u],a[u]<<=1;
当a[u] > a[v]时,a[u]-=a[v],a[v]<<=1;
当a[u]==a[v]时呢?这个应该是: a[v]-=a[u],a[u]<<=1.
为什么?因为只有这样才能保证尽量把数合并到1号位置.

标程就是这么做的,但是有问题.
md=2k 只保证最后能将所有的a[i]合成一个m,亦即保证可以把一个m分成这些a[i].但是不保证最后合成的m在a[1]位置处,亦即不保证可以把一个a[1]位置处的m分成给定的a[i].所以最后还应该加以特判.

Code

#include<cstdio> 
int gcd(int x,int y) 
{ 
    int t; 
    while(y) 
    { 
        t=x; 
        x=y; 
        y=t%y; 
    } 
    return x; 
} 
#define MAXN 10500 
int st[MAXN*100],top,n,m,T,mx,A[MAXN]; 
void _r(int& x) 
{ 
    char c=getchar(); 
    while(c<'0'||c>'9') 
    { 
        c=getchar(); 
    } 
    for(x=0;c>='0'&&c<='9';c=getchar()) 
    { 
        x=(x<<1)+(x<<3)+c-'0'; 
    } 
    return ; 
} 
int main() 
{ 
    freopen("divide.in","r",stdin); 
    freopen("divide.out","w",stdout); 
    _r(T); 
    for(int tt=1;tt<=T;tt++) 
    { 
        _r(n); 
        _r(m); 
        top=0; 
        mx=m; 
        for(int i=1;i<=n;i++) 
        { 
            _r(A[i]); 
            if(A[i]) 
            { 
                mx=gcd(mx,A[i]); 
            } 
        } 
        int t=m/mx; 
        if(t!=(t&-t)) 
        { 
            puts("no"); 
            puts("0"); 
            continue; 
        } 
        int tmp=0; 
        for(int i=1;i<=n;i++) 
        { 
            A[i]/=mx; 
        } 
        for(int p=0;p<31;p++) 
        { 
            for(int i=1;i<=n;i++) 
            { 
                if(A[i]>>p&1) 
                { 
                    if(!tmp) 
                    { 
                        tmp=i; 
                    } 
                    else 
                    { 
                        if(A[i]>=A[tmp]) 
                        { 
                            A[i]-=A[tmp]; 
                            A[tmp]=A[tmp]<<1; 
                            st[++top]=tmp; 
                            st[++top]=i; 
                        } 
                        else 
                        { 
                            A[tmp]-=A[i]; 
                            A[i]=A[i]<<1; 
                            st[++top]=i; 
                            st[++top]=tmp; 
                        } 
                        tmp=0; 
                    } 
                } 
            } 
        } 
        int q=1; 
        for(int i=1;i<=n;i++) 
        { 
            if(A[i]) 
            { 
                q=i; 
                break; 
            } 
        } 
        if(q>1) 
        { 
            puts("no"); 
            puts("0"); 
            continue; 
        } 
        puts("yes"); 
        printf("%d\n",top>>1); 
        for(;top;) 
        { 
            printf("%d %d\n",st[top--],st[top--]); 
        } 
    } 
    return 0; 
}

时间复杂度O(n*log m)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值