BUPT 2022 Spring Training #12

链接:https://vjudge.net/contest/488579

A - Agile permutation

题意

现在有一个长度为\(n\)的排列\(p_1,p_2,\cdots,p_n\)以及两种操作:
1.把两个元素交换位置,代价为\(a\)
2.随机排列整个序列,代价为\(b\)
你可以进行任意次操作。
现在给定\(n,a,b,p_1,p_2,\cdots,p_n\),求在最优策略下将排列\(p\)有序化所需代价的期望。

思路

这是一道很有意思的组合计数/概率题。
首先,如果我们进行了操作1,那么就不可能再进行操作2了,否则这一步操作1完全浪费了。那么最优策略应该是确定一个阈值\(m\),不断地进行如下迭代:

  • 若当前的排列能只用小于等于\(m\)步的操作1有序化,那么就不断地进行操作1直至完成任务
  • 若当前地排列不能只用小于等于\(m\)步地操作1有序化,那么就进行操作2.


\(E_m\)=随机一个排列,在阈值为\(m\)时将其有序化的期望代价
\(p_k\)=随机一个排列,只用操作1使其有序化所需最小步数为\(k\)的概率
那么对于当前的排列\(P\)有两种情况:

  • 若可行最小步数小于等于阈值\((k\leq m)\),则直接全部进行操作1直至完成任务,期望贡献为\(akp_k\)
  • 若可行最小步数大于阈值\((k\geq m)\),则进行一次操作2后变成了一个与原来情况完全一样的问题,期望贡献为\((1-\displaystyle \sum^{m}_{k=0}{p_k})(E_m+b)\)
    所以:

\[E_m=(0p_0+1p_1+2p_2+\cdots+mp_m)a+(1-\displaystyle \sum^{m}_{k=0}{p_k})(E_m+b) \]

\[E_m=(\displaystyle \sum^{m}_{k=0}{kp_k})a+(1-\displaystyle \sum^{m}_{k=0}{p_k})(E_m+b) \]

\[(\displaystyle \sum^{m}_{k=0}{p_k})E_m=(\displaystyle \sum^{m}_{k=0}{kp_k})a+(1-\displaystyle \sum^{m}_{k=0}{p_k})b \]

\[E_m=\frac{(\displaystyle \sum^{m}_{k=0}{kp_k})a+(1-\displaystyle \sum^{m}_{k=0}{p_k})b}{\displaystyle \sum^{m}_{k=0}{p_k}} \]

那么现在的问题就是:怎么算\(p_k\)呢?
因为\(p_k=最少经过k步操作1就能有序化的排列数/n!\),所以我们只需要求 \(最少经过k步操作1就能有序化的排列数\) 就行了。
考虑一个排列\(P\),如果采用\(k \rightarrow P_k\)的方式连边的话,排除已经就位\((k=P_k)\)的位置,剩下的应该会形成一些环,每个环内只需要交换(环的大小-1)次就能有序化。
所以对一个排列\(P\),它的最少操作1步数为\(\displaystyle \sum^{}_{环}{(环的大小-1)}\)
于是我们可以dp求解。
\(dp[k][i]\)=长度为k,最少需要i步操作1就能有序化的排列数。
现在有两种情况:

  • 这样的排列中的环不包括第k项,\(P_k=k\).
  • 这样的排列中的环包括第k项,\(P_k\neq k\)
    第一种情况较为简单,直接从\(dp[k-1][i]\)继承即可。
    第二种情况下,可以发现枚举包括第k项的环除第k项的集合可以不重不漏的枚举所有情况,且对于任何包含k的环的大小为j+1的情况,j对\(dp[k][i]\)的贡献均为

\[C_{k-1}^{j}dp[k-1-j][i-j]\times 环内排列数 \]

那么环内排列数怎么求呢?
对一个大小为j+1的环,从第1项开始填数。显然第1项有j种选择。若第一项填x,那么第x项有j-1种选择,因为既不能填x也不能填1.如果1,那么1和x就形成了一个独立的环,无法满足要求。
以此类推,如果第x项填y,那么第y项有j-2种选择。所以

\[环内排列数=j! \]

j对\(dp[k][i]\)的贡献为$$C_{k-1}^{j}dp[k-1-j][i-j]j!$$
特别的,\(dp[k][0]=1,dp[k][k]=0\)
所以\(dp\)的递推公式为:

\[ dp[k][i]=\left\{ \begin{array}{lcl} dp[k-1][i]+\displaystyle \sum^{i}_{j=1}{C_{k-1}^{j}dp[k-1-j][i-j]j!} & & {0\leq i\leq k}\\ 1 & & {i=0}\\ 0 & & {i=k} \end{array} \right. \]

进一步可得$$p_k=\frac{dp[k][n]}{n!}$$
现在我们已经求出了所有的\(E_m\),可以从0到n-1遍历所有的m,求出\(E_m\)的最小值\(E_{min}=min \{ E_0,E_1,\cdots,E_{n-1}\}\)
回到题目本身,一开始我们得到排列\(p_1,p_2,\cdots,p_n\)后有两条路可走:要么直接全用操作1,要么先走一步操作2再说.
假设一开始的排列只用操作1最少用k步可有序化,那么最终答案\(E=min \{ ka,b+E_{min}\}\)

只需要预处理组合数以及阶乘。其实比上次那个好写多了。

代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<iomanip>
#include<algorithm>
#define int long long
using namespace std;
const double INF=1e50;
const int N=30;
int c[N][N];
int jc[N];
int dp[N][N];
double p[N];
double e[N];
double em;
int lis[N];
bool vis[N];
int n,a,b;

/*long double min(long double x,long double y)
{
	if(x<y)return x;
	return y;
}*/
void init_()
{
	int k,i,j;
	double tmp1,tmp2;
	memset(c,0,sizeof(c));
	memset(dp,0,sizeof(dp));
	c[0][0]=1;
	for(k=1;k<=n;k++)
	{
		c[k][0]=1;
		for(i=1;i<=k;i++)c[k][i]=c[k-1][i-1]+c[k-1][i];
	}
	jc[0]=jc[1]=1;
	for(k=2;k<=n;k++)jc[k]=jc[k-1]*k;
	dp[0][0]=1;
	for(k=1;k<=n;k++)
	{
		dp[k][0]=1; dp[k][k]=0;
		for(i=1;i<k;i++)
		{
			dp[k][i]=dp[k-1][i];
			for(j=1;j<=i;j++)dp[k][i]+=c[k-1][j]*dp[k-1-j][i-j]*jc[j];
		}
	}
	for(k=0;k<n;k++)p[k]=(double)1.0*dp[n][k]/jc[n];
	
	em=INF;
	tmp1=tmp2=0;
	/*for(k=0;k<n;k++)
	{
		tmp1+=dp[n][k]; tmp2+=k*dp[n][k];
		e[k]=(double)1.0*(tmp2*a+(jc[n]-tmp1)*b)/tmp1;
		em=min(em,e[k]);
	}*/
	for(k=0;k<n;k++)
	{
		tmp1+=p[k]; tmp2+=k*p[k];
		e[k]=(double)1.0*(tmp2*a+(1-tmp1)*b)/tmp1;
		em=min(em,e[k]);
	}
	/*i=0;
	for(k=0;k<n;k++)
	{
		printf("%lld %lld\n",k,dp[n][k]);
		i+=dp[n][k];
	}
	printf("* %lld %lld\n",jc[n],i);*/
	return;
}
int bfs(int x)
{
	int ans=0;
	while(!vis[lis[x]])
	{
		vis[lis[x]]=true;
		x=lis[x];
		ans++;
	}
	return ans;
}
signed main()
{
	int k,i,j;
	int count=0;
	scanf("%lld%lld%lld",&n,&a,&b);
	for(k=1;k<=n;k++)scanf("%lld",&lis[k]);
	init_();
	memset(vis,false,sizeof(vis));
	for(k=1;k<=n;k++)
	{
		if(lis[k]==k)continue;
		if(!vis[k])
		{
			vis[k]=true;
			count+=bfs(k);
		}
	}
	//cout << "* " << fixed << setprecision(20) << em << endl;
	//printf("* %lld\n",count);
	printf("%0.20lf\n",(min((double)count*a,em+b)));
	//cout<<min((long double)count*a,em+b)<<endl;
	//cout << fixed << setprecision(20) << min((long double)count*a,em+b) << endl;
	return 0;
}

C - Crossed out letter

水题

I - Items in boxes

题意

\(a^{2^n} mod 2^{n+2},(1\leq a,n\leq 10^9)\)

思路

运用人类智慧感性理解一下,当n很大的时候直接输出a&1.
\(a^{2^n}\)可以看成a不断地乘自己。这样操作很多次之后膜意义下一定是越来越“靠近”1的,因为只要有一次到1了,后面就全是1了。
而且如果不是1,这玩意也很难输出。理论上这玩意可能有接近\(10^9\)位,光输出就超时了。
所以当n<30的时候就算一下,否则直接输出a&1.

代码
#include<cstdio>
#define int long long
int n,a;
signed main()
{
	int k,i,j;
	int b,c;
	scanf("%lld%lld",&n,&a);
	if(n>30)
	{
		if(a%2==0)printf("0\n");
		else printf("1\n");
		return 0;
	}
	b=4;
	for(k=0;k<n;k++)b*=2;
	for(k=0;k<n;k++)a=a*a%b;
	printf("%lld\n",a);
	return 0;
}
posted @ 2022-04-11 09:28  wild_chicken  阅读(70)  评论(0)    收藏  举报