一个听说很套路但我不会的套路:对于一个非 \(1\) 数 \(w_i\),把它看成是 \((w_i-1)+1\),于是原式变为:
\[ans=\sum_{e_1,\cdots,e_t}(n-t)!\prod_{i=1}^{t}(w_{e_i}-1) \]其中 \(\{e_1,\cdots,e_t\}\) 是 \(\{1,\cdots ,k\}\) 的一个子集,且满足 \(x_{e_1},\cdots.x_{e_t}\) 互不相同、\(y_{e_1},\cdots,y_{e_t}\) 互不相同,其实也就是枚举一种满足条件非 \(1\) 数的选法。
然后暴力枚举是 \(O(k\cdot 2^k)\) 的。
转化成二分图:在 \(x_i\) 与 \(y_i\) 之间连一条边权为 \(w_i-1\) 的边,那么我们就是要对 \(i=0,\cdots,k\) 求出选了 \(i\) 条边的匹配的价值和,其中一种匹配的价值为所有选了的边乘起来。
考虑枚举所有连通块再合并,发现一个连通块内至多有 \(k\) 条边,那么至多有 \(k+1\) 个点,那么肯定有一侧至多有 \(\lfloor\frac{k+1}{2}\rfloor\) 个点,于是我们可以状压这一侧的点,一个一个地加入另一侧的点进行 DP。时间复杂度 \(O(k^2\cdot 2^{\frac{k}{2}})\)。
实际上这个做法加一些剪枝就可以过了。
更加优秀的做法:
仍然枚举连通块再合并,设当前连通块点数为 \(s\)。若 \(s\leq \frac{2}{3}k\),我们沿用刚刚的做法;若 \(s>\frac{2k}{3}\),那么我们随便找出一个这张图的 dfs 树,显然非树边至多 \(\frac{k}{3}\) 条,那么我们可以暴力枚举非树边的选择状态,然后对于每一种状态做树形 DP。时间复杂度 \(O(k^2\cdot 2^{\frac{k}{3}})\)。
用的是 \(O(k^2\cdot 2^{\frac{k}{2}})\) 做法+剪枝:
#include<bits/stdc++.h>
#define K 55
#define N 100010
#define fi first
#define se second
#define pii pair<int,int>
#define mk(a,b) make_pair(a,b)
using namespace std;
namespace modular
{
const int mod=1000000007;
inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
inline int mul(int x,int y){return 1ll*x*y%mod;}
inline void Add(int &x,int y){x=x+y>=mod?x+y-mod:x+y;}
}using namespace modular;
inline int poww(int a,int b)
{
int ans=1;
while(b)
{
if(b&1) ans=mul(ans,a);
a=mul(a,a);
b>>=1;
}
return ans;
}
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^'0');
ch=getchar();
}
return x*f;
}
int n,k;
int fac[N];
int fa[N<<1];
int popc[33554440];
int ans[K];
vector<pii>e[N<<1];
vector<int>pl[N<<1],pr[N<<1];
int find(int x)
{
return x==fa[x]?x:(fa[x]=find(fa[x]));
}
map<int,int>dp[2][K];
void work1(int rt)
{
static int id[N<<1],sta[K];
if(pr[rt].size()<pl[rt].size()) swap(pl[rt],pr[rt]);
int idx=0;
for(int u:pl[rt]) id[u]=idx++;
sta[pr[rt].size()]=0;
for(int i=pr[rt].size()-1;i>=0;i--)
{
sta[i]=sta[i+1];
int u=pr[rt][i];
for(pii edge:e[u])
{
int v=id[edge.fi];
sta[i]|=(1<<v);
}
}
//sta[i]用于剪枝优化
bool pre=0,now=1;
dp[now][0].clear();
dp[now][0][0]=1;
for(int i=0,s=pr[rt].size();i<s;i++)
{
int u=pr[rt][i];
swap(pre,now);
for(int j=0;j<=i+1;j++) dp[now][j].clear();
for(int j=0;j<=i;j++)
{
for(auto tmp:dp[pre][j])
{
int s=tmp.fi;
Add(dp[now][j][s&sta[i+1]],tmp.se);
for(pii edge:e[u])
{
int v=id[edge.fi];
if(!((s>>v)&1)) Add(dp[now][j+1][(s^(1<<v))&sta[i+1]],mul(tmp.se,dec(edge.se,1)));
}
}
}
}
for(int i=k;i>=0;i--)
for(int j=1;j<=pl[rt].size()&&i-j>=0;j++)
Add(ans[i],mul(ans[i-j],dp[now][j][0]));
}
int main()
{
n=read(),k=read();
for(int i=1,t=(1<<25);i<t;i++) popc[i]=popc[i>>1]+(i&1);
for(int i=1;i<=(n<<1);i++) fa[i]=i;
for(int i=1;i<=k;i++)
{
int u=read(),v=n+read(),w=read();
e[u].push_back(pii(v,w));
e[v].push_back(pii(u,w));
int a=find(u),b=find(v);
assert(a<=n);
if(a!=b) fa[b]=a;
}
for(int i=1;i<=n;i++) pl[find(i)].push_back(i);
for(int i=n+1;i<=(n<<1);i++) pr[find(i)].push_back(i);
ans[0]=1;
for(int i=1;i<=n;i++)
if(find(i)==i&&pr[i].size()) work1(i);
fac[0]=1;
for(int i=1;i<=n;i++) fac[i]=mul(fac[i-1],i);
int Ans=0;
for(int i=0;i<=k;i++)
Add(Ans,mul(fac[n-i],ans[i]));
printf("%d\n",Ans);
return 0;
}
/*
3 1
1 1 2
*/
/*
10 10
3 3 367056794
6 2 124561273
1 3 46718146
6 9 415916869
10 5 985968336
3 1 526792265
1 4 386357058
10 4 349304187
2 7 102032499
3 6 502679075
*/
标签:frac,int,复杂度,cdots,零糖,ans,mod,define,XSY3405
From: https://www.cnblogs.com/ez-lcw/p/16840977.html