省选模拟赛乱改

省选模拟赛乱改

好长时间不写博客了,发篇题解证明我还活着。

游戏

给定一个正整数序列,每次在非零的元素中等概率选一个,使其减一,进行 m 次操作后,问 0 的期望个数。 n15,m200

赛时多一个等号保龄了,哈哈哈

考虑状压DP,设 fi,S 为第 i 次操作后,序列状态为 S (是否为0)的概率,有转移:

fi,S=fi1,Scount(S)+jSfi1,S/jcount(S)1×(i1+ajaSaj1)

其中组合数是确定使某一个元素变成零的操作位置。对于没有对状态产生贡献的操作,最后要统计一下方案数,包含相对位置和数量,注意边界,跑一遍背包即可。

容易发现这样做是正确的。

总复杂度 O(2nnm2) ,瓶颈在于背包,实际跑不满,也许可以证明最劣复杂度正确。

排序

考虑一种反人类的排序:

  1. 从排列的开头开始,判断每一对相邻的数是否大小关系正确。
  2. 如果存在相邻的一对数大小关系不正确:
    把较小的那个数丢到排列开头。回到步骤一。
  3. 排列有序了,结束。

现在给定步骤二的操作次数 K ,要求构造一个字典序最小的排列满足步骤二进行了恰好 K 次。 K1018

对于一个上升前缀,如果后一个元素不满足单调递增,那么它产生的贡献为 2k ,其中k是在这之前比它小的元素个数。证明是显然的,归纳即可。

构造有些小清新。

二进制拆分,从低往高位枚举,维护一个栈。若当前为1,将未使用的最小值压入栈,次小值输出;若当前为0,如果栈非空,输出栈顶,否则输出最小值。

证明就是对于每一个1,之前都输出了足够多的小于它的元素,并且以后不会再增加。

送信

诈骗题。

初始时有 n 个空信箱,排成一排,以如下方式送 m 封信:

随机选择一个信箱,在随机一个方向(左或右),从该信箱出发向该方向移动,遇到第一个空信箱就把信放入,结束。若没有空信箱就把信撕毁。

问所有信都送出的方案数。

考虑看成一个环,就是求不经过第 n+1 个点的方案数。

发现方案数很难做,改为求概率。如果最终的信箱确定,那么这些信箱没有区别。

总方案 (1mn+1)(2n+2)m

逆序对

原题P5972

一个长度为 n 排列,对于每个 k[1,n] ,找到长度为 k 的逆序对数量最少的子序列,求最小值和方案数。

原题范围 n40 ,时限6s

注意到数据范围很小,考虑优化状压DP。

首先,O(2nn) 的DP是显然的,考虑对它优化。思考,我们需要状压的原因是,后续选择的贡献与之前的状态有关,充分发扬人类智慧,发现后续未确定状态的位置把已选择的元素划分成若干段,我们不关心每个段内具体状态,只需要知道每个段包含元素个数。

举例来说,假设我们选择了 1,7,3,4 ,未确定状态的是 2,6 ,划分成三段 [1],[3,4],[7] 。考虑对状态再次压缩。

假设划分成 m 段,第 i 段元素个数为 xi ,则复杂度为 O(xin) ,显然每段相等时复杂度最劣,设每段元素个数均为 s ,考虑 s 取何值使得 sns 最大。

n 对单调性没有影响,先不理,对原式取自然对数,单调性不变,即 1sln(s) ,求导发现(我还不会求导,长大后再来探索吧),是个单峰函数,当 s=e 时取最大值,令 sN ,最大为 3n3

将状态压成一个整数即可,会多带一个n,复杂度 O(3n3n2) 。需要卡常。

CODE
#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
typedef long long ll;
const int N=42;
const int M=5e6+40;
int n,a[N],d[N],c[N],g[N],h[N],w[N][N],siz[N];
ll ans[N][N*N];
struct Hash_Table{
	int num[M],maxn;
	ll val[M];
	void ins(int x,int y,ll z){
		if(val[x]){
			if(num[x]>y)num[x]=y,val[x]=z;
			else if(num[x]==y)val[x]+=z;
		}
		else {
			maxn=max(maxn,x);
			num[x]=y;val[x]=z;
		}
	}
}f[2];
inline void get1(int x,int id){
	if(id==0){
		memset(g,0,sizeof(int)*(n+1));
		return ;
	}
	for(int i=0;i<siz[id];i++){
		g[i]=x%w[id][i];
		x/=w[id][i];
	}
}
inline int get2(int id){
	int res=0;
	for(int i=siz[id]-1;i>=0;i--){
		res=res*w[id][i]+h[i];
	}
	return res;
}
inline void get3(int len,int y){
	for(int i=0,j=0;i<len;i++){
		if(i==y)h[j-1]+=g[i];
		else h[j++]=g[i];
	}
}
signed main()
{
	// freopen("q.in","r",stdin);
	// freopen("q.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	for(int i=1;i<=n;i++){
		memcpy(c,a,sizeof(c));
		sort(c+i+1,c+n+1);
		if(i+1<=n)w[i][siz[i]++]=c[i+1];
		for(int j=i+2;j<=n;j++)w[i][siz[i]++]=c[j]-c[j-1];
		if(i==n)w[i][siz[i]++]=n+1;
		else w[i][siz[i]++]=n-c[n]+1;
	}
	for(int i=n;i>=1;i--){
		d[i]=1;
		for(int j=i+1;j<=n;j++){
			if(a[j]<a[i])d[i]++;
		}
	}
	f[0].ins(0,0,1);
	for(int i=1,flag=1;i<=n;i++,flag^=1){
		for(int it=0;it<=f[flag^1].maxn;it++){
			if(f[flag^1].val[it]){
				get1(it,i-1);
				int c=f[flag^1].num[it],s=0;
				for(int j=d[i];j<n-i+2;j++)s+=g[j];
				get3(n-i+2,d[i]);
				f[flag].ins(get2(i),c,f[flag^1].val[it]);
				h[d[i]-1]++;
				f[flag].ins(get2(i),c+s,f[flag^1].val[it]);
				f[flag^1].num[it]=f[flag^1].val[it]=0;
			}
		}
		f[flag^1].maxn=0;
	}
	for(int it=0;it<=f[n&1].maxn;it++){
		if(f[n&1].val[it]){
			get1(it,n);
			int s=0,c=f[n&1].num[it];
			s+=g[0];
			ans[s][c]+=f[n&1].val[it];
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=0;j<=i*i;j++){
			if(ans[i][j]){
				printf("%d %lld\n",j,ans[i][j]);
				break;
			}
		}
	}
}

发烧

给定 n 个字符串 si 和一个整数 k ,求每个字符串有多少子串在至少 k 个字符串出现过。 |si|105

SA都快忘干净了。

考虑把所有字符串拼在一起,中间插入互不相同的特殊字符。

对于每个后缀二分答案,判断一个答案是否合法,就是在一个区间内数颜色,判断颜色数是否不小于 k 。复杂度为 O(nlog2n)

使用双指针似乎可以做到 O(nlogn)

posted @   Abnormal123  阅读(35)  评论(4编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
历史上的今天:
2024-02-22 牛吃草 题解
2024-02-22 2024初三集训模拟测试2
点击右上角即可分享
微信分享提示