7382. 【2021.11.13NOIP提高组联考】划分
Description
有 \(n\) 个砝码,第 \(i\) 个砝码质量为 \(a_i\) 克。但砝码的质量看不清了,你只有一个长为 \(n\) 的 01 串 \(S\) 作为信息。\(S_i=1\) 当且仅当除了第 \(i\) 个砝码外的 \(n − 1\) 个砝码可以恰好划成两个质量和相同的组。给出一个合法的方案,或声明无解。本题有多组数据。你构造的质量值域应为 \([1,10^4 ]\) 。
\(n\le 50\)。
Solution
十分有趣的构造题,方法很多,这里提供一种方法。
设 01 串中,0 的个数为 \(c0\),1 的个数为 \(c1\)。先讨论最特殊的两种情况,\(c0=0\) 或者 \(c1=0\)。
当 \(c0=0\) 时,也就是字符串只由 1 构成,此时如果当 \(n\) 是奇数,全部输出 1 就可以了。反之,就无解,具体证明如下:
对于任意合法方案, 把所有 \(a_{i}\) 除以它们的最大公约数, 能转化到全体 \(a_{i}\) 最大公约数为 1 。
记总质量为 \(M\), 有解的必要条件是 \(M-a_{i}\) 均为偶数, \(a_{i}\) 必须奇偶性 相同,又不能全是偶数,那么 \(a\) 全是奇数。
此时 \(M-a_{i}\) 为奇数, 矛盾!所以无解。
当 \(c1=0\) 时,如果 \(n\) 是偶数,全输出 1 依旧合法,如果 \(n\) 是奇数,前 13 个输出 2 的若干次方,\(n>13\) 之后就全部输出 10000 即可。
再讨论一种特殊情况,\(c0=1,c1=n-1\)。如果 \(n\) 是偶数。这种情况下 1 的位置全部输出 1,0 的位置输出 2。如果 \(n\) 是奇数,在 \(n=3\) 的情况下无解,否则只用输出 3 在 0 的位置,再输出 \(n-2\) 个 1,和 1 个 \(n\)。
除去以上几种情况,其余情况全部有解。
此时分 \(c1,c2\) 的奇偶性来讨论:
- \(c1\) 奇,\(c0\) 奇。1 的位置输出 \(c1\) 个 10000,0 的位置输出 1 个 2 和 \(c0-1\) 个 1。
- \(c1\) 奇,\(c0\) 偶。1 的位置输出 \(c1\) 个 10000,0 的位置输出 \(c0\) 个 1。
- \(c1\) 偶,\(c0\) 奇。1 的位置输出 \(c1\) 个 \(2\times c0\),0 的位置输出 1 个 \(c0\),1 个 2,\(c0-2\) 个 1。
- \(c1\) 偶,\(c0\) 偶。设 \(x\) 为最接近 10000 的 \(c0\) 的倍数,则 1 的位置输出 \(c1\) 个 \(x\),0 的位置输出 \(c0\) 个 \(\frac{x}{c0}\)。
Code
#include<cstdio>
#define N 55
using namespace std;
int T,n,c0,c1,res,a[N];
bool flag;
char s[N];
int main()
{
freopen("div.in","r",stdin);
freopen("div.out","w",stdout);
scanf("%d",&T);
while (T--)
{
c0=c1=0;
scanf("%d",&n);
scanf("%s",s+1);
for (int i=1;i<=n;++i)
{
a[i]=s[i]-'0';
if (s[i]=='0') c0++;
else c1++;
}
if (c1==0)
{
printf("Yes\n");
if (n%2==0)
{
for (int i=1;i<=n;++i)
printf("1 ");
}
else
{
res=1;
for (int i=1;i<=n;++i)
{
if (res<=10000) printf("%d ",res),res*=2;
else printf("10000 ");
}
}
printf("\n");
continue;
}
if (c0==0&&n%2==1)
{
printf("Yes\n");
for (int i=1;i<=n;++i)
printf("1 ");
printf("\n");
continue;
}
if (c0==0&&n%2==0)
{
printf("No\n");
continue;
}
if (c0==1&&c1>=2&&n%2==0)
{
printf("Yes\n");
for (int i=1;i<=n;++i)
{
if (a[i]) printf("1 ");
else printf("2 ");
}
printf("\n");
continue;
}
if (c0==1&&c1>=2&&n%2==1)
{
if (n==3) printf("No\n");
else
{
printf("Yes\n");
flag=false;
for (int i=1;i<=n;++i)
{
if (!a[i]) printf("3 ");
else
{
if (!flag) printf("%d ",n),flag=true;
else printf("1 ");
}
}
}
printf("\n");
continue;
}
printf("Yes\n");
if (c1%2==1)
{
if (c0%2==1)
{
flag=false;
for (int i=1;i<=n;++i)
{
if (a[i]) printf("10000 ");
else
{
if (!flag) printf("2 "),flag=true;
else printf("1 ");
}
}
}
else
{
for (int i=1;i<=n;++i)
{
if (a[i]) printf("10000 ");
else printf("1 ");
}
}
}
else
{
if (c0%2==0)
{
int x=10000-10000%c0;
for (int i=1;i<=n;++i)
{
if (a[i]) printf("%d ",x);
else printf("%d ",x/c0);
}
}
else
{
bool f1=false,f2=false;
for (int i=1;i<=n;++i)
{
if (a[i]) printf("%d ",c0*2);
else
{
if (!f1) printf("%d ",c0),f1=true;
else if (!f2) printf("2 "),f2=true;
else printf("1 ");
}
}
}
}
printf("\n");
}
return 0;
}