#C241021B. 仓皇逃跑


#C241021B. 仓皇逃跑

标签(Label)

  • 动态规划
  • 容斥原理
  • 状态压缩
  • 逆向思维

网址(Website)

题目详情 - 仓皇逃跑 - Super

题解 - 仓皇逃跑 - Super

题解(Solution)

Subtask 分析

肯定会考虑到状态压缩。

Subtask 1:

容易推出,设路径长度为 $l$,则答案为 $k^{n-1-l} \times (k^l - k)$。

$\qquad$ 解释:这里 $k^{n-1-l}$ 表示不在路径上的边颜色选择的方案数,$k^l - k$ 表示路径上颜色的选择方案数(排除全部颜色相同的情况)。

Subtask 2:

还是容斥,正向我想出来之后 $\text{WA}$ 了,反向想要简单得多。$n-1$ 条边随便选的方案数是 $k^{n-1}$,一条路径全部相同的方案数是 $k^{n-l}$,两条路径就考虑去掉重合的方案数。即先分别减去两条路径颜色全部相同的方案数(令路径长度分别为 $l_1, l_2$),再加上两条路径所有边颜色均相同的方案数 $k^{n-l_1-l_2+l_\text{重合}}$,就是最后的答案。直接 $\text{dfs}$ 可以轻易求出重合的边的长度。

$\qquad$ 解释:通过容斥原理,先考虑所有可能的组合,然后减去重复计算的部分,最后加上多减去的部分。

Subtask 3:

和 Subtask 1 的一样,$\prod{(\text{每条路径的贡献})} \times {\text{不在路径上的边颜色随便选的贡献}}$。

$\qquad$ 解释:每条路径的贡献乘起来,再乘以不在路径上的边颜色选择的方案数。

Subtask 4:

直接暴力跑即可,枚举每条边的颜色。

$\qquad$ 解释:由于数据量可能不大,直接枚举所有可能的颜色组合。

Subtask 5:

从 Subtask 2 可以顺带推出,考虑状态压缩枚举 $m$ 条边是否存在,由容斥原理得如果路径条数数是偶数,就从答案中减去计算出来的方案数,否则就加上方案数。每次方案数的计算还是和 Subtask 2 中 $k^{n-l_1-l_2+l_\text{重合}}$ 类似,即 $k^{n-\vert E_1 \bigcup E_2 \bigcup \cdots \bigcup E_c \vert}$,其中 $c$ 表示路径条数,$E_i$ 表示第 $i$ 条路径的边集(应该不会有人不知道 $\vert E_i \vert$ 表示的是集合的大小吧?)。

$\qquad$ 时间复杂度:$O(nm2^m)$。

$\qquad$ 解释:通过状态压缩枚举边的存在性,利用容斥原理计算所有可能的路径组合,然后根据路径数量的奇偶性决定是加还是减方案数。

出题者题解

算法分析

Subtask 1

首先合法路径上至少有两条边颜色不同,这不太好统计,我们可以反过来想:

合法方案数 = 总方案数 - 不合法的方案数

总方案数显然是 $k^{n-1}$,考虑计算不合法方案数。

先考虑 $m=1$ 的情况:

显然,不合法方案的这一条路径上的颜色都是相同的,我们就想到像缩点一样,把这一条路径的边缩成一条边,这样不合法方案数就好算了,设这一条路径包含 $c$ 条边,则不合法方案数为 $k^{n-c}$。

Subtask 1+3

考虑树上差分,知道每条边被算了几次,没算的边拎出来,记为 $cnt$。对于一条路径 $u,v$,合法情况就是 $k^{dis(u,v)}-k$,全部相乘,最后再乘上 $k^{cnt}$。

Subtask 4

直接暴搜每条边,统计答案。

满分做法

还是接着最早的思路想,来考虑不合法的方案。

如果 $m \geq 2$,这样算会重复计算多条路径同时不合法的方案,我们发现 $n \leq 60, m \leq 15$,于是可以直接暴力枚举容斥。

具体来说,就是枚举不合法路径的集合 $S$,对于每条路径,把它所有边合并到某一条边上(如果路径有相交,当然是合并成一条边了)。然后统计合并后的边数 $cnt$,当 $|S|$ 为偶数,让答案加上 $cnt$,当 $|S|$ 为奇数,让答案减去 $cnt$。枚举路径集合可以直接状态压缩,合并可以用并查集实现,时间复杂度

为 $O(nm2^m)$。

代码(Code)

#include<bits/stdc++.h>
#include<vector>
#define For(i,l,r) for(int i=l;i<=r;i++)
#define Rof(i,l,r) for(int i=l;i>=r;i--)
using namespace std;
#define P pair<int,int>
#define int long long
#define x first
#define y second
inline int rd(){
	char c;bool f=false;while(!isdigit(c=getchar()))f=c=='-';int x=c^48;
	while(isdigit(c=getchar())){x=(((x<<2)+x)<<1)+(c^48);}return f?-x:x;
}const int inf = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9+7;
const int N = 128;
const int S = (1<<16);
bool ST;

vector<int> ft[N],e[N];
P road[N];
int n,m,k,ans;

inline void add(int &x,int y){if((x+=y)>=mod)x-=mod;}
int fa[N];inline int find(int x){
	if(fa[x]==x) return x;
	return fa[x] = find(fa[x]);
}

int dep[N],f[N];
void dfs_dep(int x,int F){
	dep[x] = dep[F]+1, f[x]=F;
	for(auto y:ft[x]) if(y^F) dfs_dep(y,x);
}

int bas[N];
void Solve(){
	n = rd(), m = rd(), k = rd();
	For(i,1,n-1){
		int x = rd(), y = rd();
		ft[x].emplace_back(y);
		ft[y].emplace_back(x);
	}For(i,1,m) road[i] = {rd(), rd()};
	dfs_dep(1,0);
	For(i,1,m){
		int x,y;tie(x,y)=road[i];
		if(dep[x]<dep[y]) swap(x,y);
		while(dep[x]>dep[y]) e[i].emplace_back(x),x=f[x];
		while(x!=y) e[i].emplace_back(x),e[i].emplace_back(y),x=f[x],y=f[y];//记录每条路经 
	}
	bas[0] = 1;For(i,1,n) bas[i] = bas[i-1] * k % mod;
	ans = bas[n-1];
	For(s,1,(1<<m)-1){
		int c = __builtin_popcountll(s);
		iota(fa,fa+n+1,0);
		int all=0;
		For(i,1,m){//枚举每一条路径 
			if((s>>(i-1))&1){
				For(j,1,(int)e[i].size()-1){
					int fx = find(e[i][0]);
					int fy = find(e[i][j]);
					fa[fx] = fy;
				}
			}
		}For(i,2,n) all+=fa[i]==i;
		if(c&1) add(ans, mod-bas[all]);
		else add(ans, bas[all]);
	}printf("%lld\n",ans);
}

bool ED;
signed main(){
	cerr<<abs(&ST-&ED)/1024./1024.<<"MB\n";
	freopen("escape.in","r",stdin);
	freopen("escape.out","w",stdout);
	int T = 1, Tim = clock();
	while(T--) Solve();
	return cerr<<"TIME:"<<(clock()-Tim)/1000.,0;
}

文章作者: WolfDeer
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 WolfDeer !
  目录