题解 P6017 【[CSGRound3]仙人掌】
前言
这是 CSGRound 3 的 E 题,在月赛上只有
做法1:
暴力枚举仙人掌,可以过第一个 subtask1。说不定加点剪枝可以过 subtask2。
做法2:
考虑把度数序列中所有元素放进一个 multiset,可以发现这样合法的 multiset 非常少,而每一个合法的 multiset 对应的所有的度数序列都是合法的。 所以可以用园方树进行 dp,这样可以过 subtask1,subtask2。或许可以过 subtask3。
做法3:
对于一棵树的情况,只要所有点度数都至少为
做法4:
对于
做法5:
考虑什么情况下仙人掌能构造出来。 定义一个函数
即
大致证明如下:
如果边数不在这个范围内,显然不合法。
如果度数和正好等于
那么之前的条件依然满足,问题变成了一个形式相同,规模更小的子问题,可以归纳证明。 对于存在奇数的情况,可以发现如果合法那么一定存在一种不连环的方案使得把它缩点之后变成全是偶数的情况,且在偶数的情况下合法。
那么消奇数的过程每连一条边就相当于度数和减少
做法6:
用组合数优化做法5,可以直接AC。
remark:
subtask7 出题人暂时想不到特殊做法,如果又会做法的可以私信出题人。
Code
#include <cstdio>
#include <algorithm>
#define mo 998244353
using namespace std;
int C[5005][5005];
bool check(int n, int m, int a, int b) {
if (m == n - 1)
return 1;
if (a > b)
m -= a, n -= a;
else
m -= (a + b) >> 1, n -= (a + b) >> 1;
if (m < n)
return 0;
if (m > (n - 1) / 2 * 3 + (n - 1) % 2)
return 0;
return 1;
}
int main() {
int cas;
scanf("%d", &cas);
C[0][0] = 1;
for (int i = 1; i <= 5000; i++) {
C[i][0] = 1;
for (int j = 1; j <= i; j++) C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mo;
}
while (cas--) {
int n, m;
scanf("%d%d", &n, &m);
if (m > (n - 1) / 2 * 3 + (n - 1) % 2) {
puts("0");
continue;
}
if (n == 1) {
puts("0");
continue;
}
if (n == 2) {
if (m == 1)
puts("1");
else
puts("0");
continue;
}
int ans = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j <= n - i; j++) {
if (i + j * 3 > m + m)
continue;
if (!check(n, m, i, j))
continue;
int m1 = m + m - i - j, n1 = n - i;
if (m1 & 1)
continue;
ans = ((long long)C[m1 / 2 - 1][n1 - 1] * C[n][n1] % mo * C[n1][j] + ans) % mo;
}
}
printf("%d\n", ans);
}
return 0;
}
注:在此给出验题人的代码。