YCH的模拟赛 T2

首先这个期望就是吓唬人的,因为输出格式把期望消掉了

其实就是求总的步数

很容易想到如果通过连边的方式,会出现很多环,每一个环的长度(l_i)就表示这个环里面任意一个点经过(l_i)次变换可以变回它自己

先看一种做法:

假设每一种循环节长度都用到了

我们设每一个环的长度(l_i),个数(n_i)

假如每一个环里面都有至少一个点被选择了,那么答案就是(lcm(l_1...l_n))

每一位有(n_1+...+n_k)种填法

(k)表示长度不同的环的个数

一共((n_1+...+n_k)^n imes lcm(l_1...l_m))

这个算法对不对?

其实不对。这种方法算出来的答案会大很多

因为这样算,有可能会有没有用到的环

那是不是就需要容斥来消掉不合法的?

怎么容斥?

假设不选(l_1),每一位有(n_2+...+n_k)种填法,一共有((n_2+...+n_k)^n)种选法

假设不选(l_2),每一位有(n_1+n_3+...+n_k)种填法,一共有((n_1+n_3+...+n_k)^n)种选法

其他的也是同理,最后把这些不选一种的情况减掉。

发现又减多了,再把不选三种的加上,把不选四种的减去...

这样就能算出k种循环节都被选了的方案数,然后乘上他们的lcm

那么最后的做法就是 状压+容斥

2^k枚举选择哪些循环节长度的字符,把他们都加起来

那么k的范围是多少?

(kleq 14)

因为(1+...+14>100)

总复杂度(O(3^k))

#include<cstdio>
#include<cstdlib>
#include<cstring>

using namespace std;

#define inc(a,b) {a+=b;if(a>=mo) a-=mo;}
#define dec(a,b) {a-=b;if(a<0) a+=mo;}

const int maxn=110;
const int mo=1000000007;

int n,m,y[maxn],z[maxn][2],cnt[maxn];

bool vis[maxn];

int gcd(int a,int b)
{
	if(!b) return a;
	else return gcd(b,a%b);
}

int mul(int a,int b)
{
	if(b==0) return 1;
	int ans=1;
	while (b)
	{
		if(b&1) ans=1ll*ans*a%mo;
		a=1ll*a*a%mo;
		b>>=1;
	}
	return ans;
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int a=1;a<=m;a++)
		scanf("%d",&y[a]);
	for(int a=1;a<=m;a++)
		if(!vis[a])
		{
			int p=y[a],num=1;
			while (p!=a)
			{
				vis[p]=true;
				p=y[p];
				num++;
			}
			vis[a]=true;
			cnt[num]+=num;
		}
	int q=0;
	for(int a=1;a<=m;a++)
		if(cnt[a])
		{
			z[q][0]=a;
			z[q][1]=cnt[a];
			q++;
		}
	int ans=0;
	for(int a=1;a<(1<<q);a++)
	{
		int num1=0,lcm=1,v=0;
		for (int b=0;b<q;b++)
			if (a&(1<<b)) 
			{
				num1++;
				lcm=lcm*z[b][0]/gcd(lcm,z[b][0]);
			}
		for(int b=a;b;b=(b-1)&a)
		{
			int num2=0,lcm=1,total=0;
			for(int c=0;c<q;c++)
				if(b&(1<<c))
				{
					num2++;
					lcm=lcm*z[c][0]/gcd(lcm,z[c][0]);
					total+=z[c][1];
				}
			if((num1&1)!=(num2&1)) dec(v,mul(total,n))
			else inc(v,mul(total,n));
		}
		inc(ans,1ll*v*lcm%mo);
	}
	printf("%d
",ans);

	return 0;
}
原文地址:https://www.cnblogs.com/lcezych/p/13266811.html