省选模拟赛乱改
好长时间不写博客了,发篇题解证明我还活着。
游戏
给定一个正整数序列,每次在非零的元素中等概率选一个,使其减一,进行 \(m\) 次操作后,问 \(0\) 的期望个数。 $ n \le 15 , m\le 200 $
赛时多一个等号保龄了,哈哈哈
考虑状压DP,设 $ f_{i,S} $ 为第 \(i\) 次操作后,序列状态为 \(S\) (是否为0)的概率,有转移:
其中组合数是确定使某一个元素变成零的操作位置。对于没有对状态产生贡献的操作,最后要统计一下方案数,包含相对位置和数量,注意边界,跑一遍背包即可。
容易发现这样做是正确的。
总复杂度 $ O(2^n n m^2) $ ,瓶颈在于背包,实际跑不满,也许可以证明最劣复杂度正确。
排序
考虑一种反人类的排序:
- 从排列的开头开始,判断每一对相邻的数是否大小关系正确。
- 如果存在相邻的一对数大小关系不正确:
把较小的那个数丢到排列开头。回到步骤一。 - 排列有序了,结束。
现在给定步骤二的操作次数 \(K\) ,要求构造一个字典序最小的排列满足步骤二进行了恰好 \(K\) 次。 $ K\le 10^{18} $
对于一个上升前缀,如果后一个元素不满足单调递增,那么它产生的贡献为 $ 2^k $ ,其中k是在这之前比它小的元素个数。证明是显然的,归纳即可。
构造有些小清新。
二进制拆分,从低往高位枚举,维护一个栈。若当前为1,将未使用的最小值压入栈,次小值输出;若当前为0,如果栈非空,输出栈顶,否则输出最小值。
证明就是对于每一个1,之前都输出了足够多的小于它的元素,并且以后不会再增加。
送信
诈骗题。
初始时有 \(n\) 个空信箱,排成一排,以如下方式送 \(m\) 封信:
随机选择一个信箱,在随机一个方向(左或右),从该信箱出发向该方向移动,遇到第一个空信箱就把信放入,结束。若没有空信箱就把信撕毁。
问所有信都送出的方案数。
考虑看成一个环,就是求不经过第 \(n+1\) 个点的方案数。
发现方案数很难做,改为求概率。如果最终的信箱确定,那么这些信箱没有区别。
总方案 $ (1-\frac{m}{n+1}) (2n+2)^{m} $
逆序对
原题P5972
一个长度为 \(n\) 排列,对于每个 $ k \in [1,n] $ ,找到长度为 \(k\) 的逆序对数量最少的子序列,求最小值和方案数。
原题范围 $ n\le 40 $ ,时限6s
注意到数据范围很小,考虑优化状压DP。
首先,$ O(2^n n) $ 的DP是显然的,考虑对它优化。思考,我们需要状压的原因是,后续选择的贡献与之前的状态有关,充分发扬人类智慧,发现后续未确定状态的位置把已选择的元素划分成若干段,我们不关心每个段内具体状态,只需要知道每个段包含元素个数。
举例来说,假设我们选择了 $ 1,7,3,4 $ ,未确定状态的是 $ 2,6 $ ,划分成三段 $ [1] , [3,4] , [7] $ 。考虑对状态再次压缩。
假设划分成 $ m $ 段,第 \(i\) 段元素个数为 $ x_i $ ,则复杂度为 $ O( \prod x_i n ) $ ,显然每段相等时复杂度最劣,设每段元素个数均为 \(s\) ,考虑 \(s\) 取何值使得 $ s^{\frac{n}{s}} $ 最大。
\(n\) 对单调性没有影响,先不理,对原式取自然对数,单调性不变,即 $ \frac1{s} ln(s) $ ,求导发现(我还不会求导,长大后再来探索吧),是个单峰函数,当 $ s=e $ 时取最大值,令 $ s \in N $ ,最大为 $ 3^{\frac{n}{3}} $
将状态压成一个整数即可,会多带一个n,复杂度 $ O(3^{\frac{n}{3}} n^2) $ 。需要卡常。
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 $ 个字符串 $ s_i $ 和一个整数 \(k\) ,求每个字符串有多少子串在至少 \(k\) 个字符串出现过。 $ \sum | s_i | \le 10^5 $
SA都快忘干净了。
考虑把所有字符串拼在一起,中间插入互不相同的特殊字符。
对于每个后缀二分答案,判断一个答案是否合法,就是在一个区间内数颜色,判断颜色数是否不小于 \(k\) 。复杂度为 $ O(n \log^2 n) $
使用双指针似乎可以做到 $ O(n\log n) $