分球 (divide)
来源
???不可说
题面
数据范围
30%的数据满足:其中的测例如果有解,最少操作步数不超过8步
另有20%的数据满足:将每组测例的ai从小到大排序后的序列bn必有:
b1=b2=……=bn或b1=b2,bi=2bi−1(i≥3)
100%的数据满足:1≤n≤10000,1≤m<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)