终于做出一道AGC的B了!!(虽然好像只打了一场AGC
传送门
题意
有N个橘子,每个橘子有一个重量w, 现在A先开始拿第一个, 如果A的重量比B大,那么就到B开始拿。
问有多少种橘子的排列使得拿完所有橘子后, AB拿的重量相等。
思路
一开始就是各种dp, 就是那种往排列里面插数的dp,很绝望。
然后就是考虑贪心, 搜索+剪枝, 和神奇的结论,但显然不行。
后来开始考虑发过来构造,开始思考既然两个人拿的一样多, 那是不是只要把橘子分成两份一样多的就行了。
题解
好欸, 就是这样。
你想啊,我们把橘子分成两堆一样多的, 保证a拿到都是堆1的, b都是2的。
这样重量是一样的, 但是能保证一定有这样的排列吗?
可以的, A拿第一个, 我们把堆1的第一个放入排列, 如果A比B多, 那么就依次放堆2的, 再到堆1。
因为两堆橘子都会拿完(重量一样,读者可自行证明。)所以一定可以产生合法排列。
但不是唯一的哦, 假设堆1是(1, 2, 3)A可以先拿1, 也可以先拿2...这样产生的排列是不一样的。
所以, 我们把所有橘子划分成相同的两堆排列, (1, 2, 3)表示先A拿1,再拿2...(2, 3, 1)则算另一种分法, 对于每一种分法, 我们只能构造出唯一的合法序列(读自证, 所以答案就是划分方案数。
然后开始dp, (f_{i, j, sum})表示在前i个橘子种拿了j个,总重量为sum的方案数。
这个dp转移显然,读者自己写
处理出这个dp数组后, 枚举一共拿了i个橘子, 和为全部橘子重量一半的方案数(记为fi
预处理阶乘fact数组。
那么对于每个i, (ans) += (f_{i} * (fact[i] * fact[n-i]))
详细的说, fi表示把橘子分成两堆,堆1有i个, 堆2有n-i个。的方案数
对于每种方案, 堆1显然有(i!)种排列, 堆2有((n-i)!)种。
如果还不懂,看看代码,或者自己思考一下?
是不是太详细了
但这道题的我考场确实没太想明白(虽然a了, 但oi不能自我欺骗。
要把每一步的证明都思考明白
for(int i=1; i<=n; i++){
f[i-1][0][0] = 1;
for(int j=1; j<=i; j++){
for(int k=0; k<=N*N; k++){
f[i][j][k] = f[i-1][j][k];
if(w[i] <= k) f[i][j][k] += f[i-1][j-1][k-w[i]];
f[i][j][k] %= mod;
}
}
}
p[1] = 1;
for(int i=2; i<=n; i++){
p[i] = p[i-1] * i;
p[i] %= mod;
}
if(sum % 2){
printf("0
");
return 0;
}
sum/=2;
ll ans = 0;
for(int i=1; i<=n; i++){
ans += f[n][i][sum] * ((p[i] * p[n-i])%mod);
ans %= mod;
}
printf("%lld
", ans);