【XSY3405】零糖麦片(二分图,复杂度均衡)

一个听说很套路但我不会的套路:对于一个非 \(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
*/
posted @ 2022-10-30 12:19  ez_lcw  阅读(17)  评论(0编辑  收藏  举报