也就是说,这个二分图是由许多条不相交的链组成的(所谓的不相交是指没有属于不同链的两条边连在一个点上),那么我们要求的其实就是这个二分图的补图的匹配方案数。(补图简单的定义就是,原二分图中有的边它没有,原来没有的边他有。)
我们令g[i]表示在这个二分图中选取i条边的方案数(也就是等于确定了i个不合法的情况)
那么我们得到答案有如下式子:
来解释一下,首先(n-i)!表示一个全排列,就是因为你当前确定有i条边是不合法的,那么剩余的n-i条边就是自由排列的,那就是它的阶乘次的方案数。后面的是什么意思呢?首先我们知道如果啥都不管的话,那么n!是所有的情况,但是你在n!种的情况之中,必然会统计到有一条边是不合法的情况,于是乎我们就要去计算有一条边不合法的情况,然后把它减掉。但是在这样计算的时候又会多把有两条边不合法的情况减掉,相当于多减了,然后我们还得给加回来……所以这样层层递推就有了最后的式子。
然后我们去求g[i],用f[i][j][0/1]表示在一条链上取到第i个点,取了j条链,当前点取或者没取的情况数,那么就有转移:
f[i][j][0] = f[i-1][j][1] + f[i-1][j][0];
f[i][j][1] = f[i-1][j-1][0];
最后我们直接用f把g合并出来就可以了。时间复杂度是O(n^2/k + n)?能过95.最后一个点什么NTT真的不会……
然而这个也不想写……
40pts状压代码:
#include #include #include #include #include #include #include #include
看一下学姐的代码。
#include #include #include #include #include #define fi first#define se second#define pii pair #define mp make_pair#define enter putchar('\n')#define space putchar(' ') //#define ivorysi#define MAXN 100005typedef long long int64;using namespace std;template void read(T &res) { res = 0;char c = getchar();T f = 1; while(c < '0' || c > '9') { if(c == '-') f = -1; c = getchar(); } while(c >= '0' && c <= '9') { res = res * 10 + c - '0'; c = getchar(); } res *= f;}template void out(T x) { if(x < 0) {x = -x;putchar('-');} if(x >= 10) { out(x / 10); } putchar('0' + x % 10);}const int MOD = 998244353;int inc(int a,int b) { return a + b >= MOD ? a + b - MOD : a + b;}int mul(int a,int b) { return 1LL * a * b % MOD;}void update(int &x,int y) { x = inc(x,y);}int fac[MAXN],N,K,g[MAXN],f[2005 * 2][2005][2];bool vis[2][MAXN];void DP(int st,int cnt) { for(int j = 0 ; j <= N ; ++j) update(f[st + 1][j][0],inc(f[st][j][0],f[st][j][1])); for(int h = st + 2 ; h <= st + cnt ; ++h) { for(int j = 0 ; j <= N ; ++j) { update(f[h][j][0],inc(f[h - 1][j][1],f[h - 1][j][0])); if(j >= 1) update(f[h][j][1],f[h - 1][j - 1][0]); } }}void Solve() { read(N);read(K); fac[0] = 1; for(int i = 1 ; i <= N ; ++i) fac[i] = mul(fac[i - 1],i); int tot = 0; memset(vis,0,sizeof(vis)); f[0][0][0] = 1; for(int i = 1 ; i <= N ; ++i) { if(!vis[0][i]) { int cnt = 0; for(int j = i ; j <= N ; j += 2 * K) { vis[0][j] = 1; cnt++; if(j + K <= N) {vis[1][j + K] = 1;++cnt;} } DP(tot,cnt); tot += cnt; } if(!vis[1][i]) { int cnt = 0; for(int j = i ; j <= N ; j += 2 * K) { vis[1][j] = 1; ++cnt; if(j + K <= N) { vis[0][j + K] = 1;++cnt; } } DP(tot,cnt); tot += cnt; } } for(int i = 0 ; i <= N ; ++i) { g[i] = inc(f[2 * N][i][0],f[2 * N][i][1]); } int t = 1,ans = 0; for(int i = 0 ; i <= N ; ++i) { update(ans,mul(t,mul(g[i],fac[N - i]))); t = mul(t,MOD - 1); } out(ans);enter;}int main() {#ifdef ivorysi freopen("f1.in","r",stdin);#else freopen("permutation.in","r",stdin); freopen("permutation.out","w",stdout);#endif Solve(); return 0;}
T2.tree
期望得分30,实际得分40
这题大概看了20多分钟,没什么思路,但是可以疯狂爆搜,暴力枚举哪两条边要删除,然后暴力dfs判断图是否联通,复杂度O(n^3),得分40.
这道题的60pts做法是枚举每一条原来的树边,之后对新图跑他让tarjan去求桥,每座非树边的桥有1的贡献。
然后满分的做法就是,我们发现对于一条树边,一棵子树内的所有节点如果有两条或者以上连了出去,那么你怎么割也无法割断,如果只有一条,那么就有1的贡献,如果没有,那就随便找一条割断,也就是有m的贡献。
所以我们可以进行树上差分。对于每一条新加的边,我们把它拆成从一个点到LCA和另一个点到LCA的两条路径,分别差分维护。最后我们统计一下,每个点的点权为1则有1的贡献,为0有m的贡献。
我写的时候是用树剖的,也能过300000.
然后学姐还有更强的操作,直接维护dfs序,统计一个子树管辖的区间之内能向左/右延伸的最远的两条边能延伸到的范围,最后统计的时候如果有两天或以上的边,你就割不断,有一条贡献为1,0条则贡献为m。(%学姐orz)
40pts爆搜不看了,直接上100的树剖。
#include #include #include #include #include #include #include #include
T3.polynomial
期望得分30,实际得分0.
这道题是真心不可做……题解里面什么FFT是搞哪样……
考试的时候暴力打表n<=4的情况,结果发现自己推4个一样的时候推错了,多推了3个,爆零。
还是放上改好的暴力打表30吧……正解代码11kb又是哪样……
#include #include #include #include #include #include #include #include
感觉自己还是太弱,不知道明天能咋样orz。