6595 Everything Is Generated In Equal Probability
题意
给定一个\(n\),从\([1,n]\)中等概率取出一个数,再等概率生成一个\(n\)的全排列,再计算这个全排列的函数值,求这个函数值的期望。
函数表达为输入一个全排列,计算其逆序数,再等概率取出一个子序列(可以是空,可以是原序列),递归计算该子序列的函数值,累加返回。
分析
- 暴力求出样例输出对应的分数,发现前三个答案就是\(\frac{0}{9},\)\(\frac{3}{9}\)和\(\frac{8}{9}\),然后瞎猜几个规律(\(\frac{n^2-1}{9}\))也许就过了...
- 正解和证明:
- 首先一个长度为\(n\)的全排列,逆序数的期望应该是\(\frac{C_n^2}{2}\),即任取两个数产生逆序数的期望为\(\frac{1}{2}\)
- 同时注意到全排列的子序列离散化之后也是一个全排列,因此这个递归的函数我们可以转化为递推式子
- 设\(f[i]\)表示取出数字为\(i\)所得到的函数值,因此\(f[i]=\frac{C_i^2}{2}+\frac{1}{2^i}\sum_{j=0}^iC_i^jf[j]\)。
- 左右边都有\(f[i]\),将右边拆开并移项,得到\(f[i]=\frac{2^{i-1}}{2^i-1}\frac{C_i^2}{2}+\frac{1}{2^i-1}\sum_{j=0}^{i-1}C_i^jf[j]\)(网上的题解基本都是推错的...官方题解也写的很模糊...),而我们要求的答案就是\(g[n]=\frac{1}{n}\sum_{i=1}^nf[i]\)。
- 根据\(f[i]\)的递推应该能推出通项?不过感觉最好的方法应该是根据样例即\(g[1],g[2],g[3]\)推出接下来几项找规律更容易。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
ll Pow(ll a,ll n){
ll ans=1ll;
while(n){
if(n%2){
ans=ans*a%mod;
}
a=a*a%mod;
n/=2;
}
return ans;
}
ll inv(ll x){
return Pow(x,mod-2)%mod;
}
ll n;
int main(void){
ll inv9=inv(9);
while(~scanf("%lld",&n)){
printf("%lld\n",(Pow(n,2)-1)*inv9%mod);
}
return 0;
}
6599 I Love Palindrome String
题意
给定一个字符串,询问\(1-n\)每种长度的回文子串有多少个,且必须满足前半部分也是回文串。
分析
- 回文树可以求出每个本质不同的回文串长度和个数,且最多只有\(len(S)\)个,然后枚举每个回文子串,再判断前半部分是否是回文串。
- 回文串要判断前半部分是否回文,只需判断前半部分和后半部分是否相等即可,可以用字符串哈希。
- 所以这是一道回文树模板题,可以用来修正板子。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned ull;
const int N=3e5+50;
const ull base=19260817;
int ans[N];
ull has[N],pw[N];
void initHash(){
pw[0]=1;
for(int i=1;i<N;i++){
pw[i]=pw[i-1]*base;
}
}
ull getHash(int l,int r){
return has[r]-has[l-1]*pw[r-l+1];
}
//判断回文串[l,r],前后两部分是否相等,若是,说明是回文
bool check(int l,int r){
int len=r-l+1;
int mid=(l+r)/2;
return getHash(l,mid)==getHash((len%2?mid:mid+1),r);
}
struct PT{
int next[N][26],fail[N],cnt[N],num[N],len[N];
int S[N],last,id[N],n,p;
int newnode(int l){
for(int i=0;i<26;i++){
next[p][i]=0;
}
cnt[p]=num[p]=0;
len[p]=l;
return p++;
}
void init(){
p=0;
//先0再-1 这样-1(p=1)的儿子就是0(p=0),后面0(p=0)的fail就是-1(p=1)
newnode(0);
newnode(-1);
last=0;
n=0;
S[n]=-1;
fail[0]=1;
}
int getFail(int x){
while(S[n-len[x]-1]!=S[n]){
x=fail[x];
}
return x;
}
void add(int c){
c-='a';
S[++n]=c;
int cur=getFail(last);
if(!next[cur][c]){
int now=newnode(len[cur]+2);
fail[now]=next[getFail(fail[cur])][c];
num[now]=num[fail[now]]+1;
next[cur][c]=now;
}
last=next[cur][c];
cnt[last]++;
id[last]=n;
}
void count(){
for(int i=p-1;i>=0;i--){
cnt[fail[i]]+=cnt[i];
}
}
void getAns(){
for(int i=2;i<p;i++){
//枚举每个节点
if(check(id[i]-len[i]+1,id[i])){
ans[len[i]]+=cnt[i];
}
}
}
}ac;
char s[N];
int main(void){
// freopen("in.txt","r",stdin);
initHash();
while(~scanf("%s",s+1)){
int n=strlen(s+1);
ac.init();
has[0]=0;
for(int i=1;i<=n;i++){
ac.add(s[i]);
//这里记得别写成*10
has[i]=has[i-1]*base+(s[i]-'a');
}
memset(ans,0,sizeof(ans));
ac.count();
ac.getAns();
for(int i=1;i<=n;i++){
printf("%d%c",ans[i],i==n?'\n':' ');
}
}
return 0;
}
6600 Just Skip The Problem
题意
通过询问确定一个数\(x\),每次询问\(y_i\),回答\(x\&y_i\)是否等于\(y_i\),问最少询问次数的方案数。
分析
显然是每次询问确定一位是最优的,可以通过计算期望证明。
所以\(x\)有\(n\)位数,就是有\(n!\)种不同的询问排列来确定这个数。
当\(n>1e6+3\)时,\(n!\)肯定含有因子\(1e6+3\),因此结果为0。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e6+3;
ll n;
int main(void){
while(~scanf("%lld",&n)){
//n>=1e6+3 n!肯定含有1e6+3这个因子,所以结果一定为0
if(n>=1000003){
printf("0\n");
continue;
}
ll ans=1ll;
for(ll i=2ll;i<=n;i++){
ans=ans*i%mod;
}
printf("%lld\n",ans);
}
return 0;
}
6601 Keen On Everything But Triangle
题意
给n个棍子,多次询问区间内能组成三角形的最大周长。
分析
如果不考虑不能组成三角形的情况,那么答案就是区间内前三大的值之和。
考虑有不能组合的情况,仍然是从大到小贪心地判断下一组能否组成三角形。
长度在1e9范围内,连续判断的次数不会超过44次。
证明就是斐波那契数列,假设连续的\(n\)个长度都不能组成三角形(1,1,10000000...这种肯定可以),假设长度增长很慢,每次都只是刚好前两个木棍的和,那么这就是斐波那契数列,而第45项斐波那契数列已经超过了1e9
所以用主席树查询区间第k小。
代码
#include <bits/stdc++.h>
using namespace std;
#define mid (l+r)/2
typedef long long ll;
const int N=1e5+50;
int tr[N*30],sum[N*30],lr[N*30],rr[N*30];
int n,q,a[N],b[N],l,r;
int cnt;
int build(int l,int r){
int rt=++cnt;
sum[rt]=0;
if(l==r){
return rt;
}
lr[rt]=build(l,mid);
rr[rt]=build(mid+1,r);
return rt;
}
int update(int pre,int l,int r,int x){
int rt=++cnt;
lr[rt]=lr[pre];
rr[rt]=rr[pre];
sum[rt]=sum[pre]+1;
if(l==r){
return rt;
}
if(x<=mid){
lr[rt]=update(lr[pre],l,mid,x);
}else{
rr[rt]=update(rr[pre],mid+1,r,x);
}
return rt;
}
int kth(int a,int b,int l,int r,int k){
if(l>=r){
return l;
}
int x=sum[lr[b]]-sum[lr[a]];
if(k<=x){
return kth(lr[a],lr[b],l,mid,k);
}else{
return kth(rr[a],rr[b],mid+1,r,k-x);
}
}
int main(void){
// freopen("in.txt","r",stdin);
while(~scanf("%d%d",&n,&q)){
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
b[i]=a[i];
}
//权值建树记得离散化
sort(b+1,b+1+n);
int m=unique(b+1,b+1+n)-b-1;
//多组记得初始化
cnt=0;
//离散化后范围记得是m
tr[0]=build(1,m);
for(int i=1;i<=n;i++){
a[i]=lower_bound(b+1,b+1+m,a[i])-b;
tr[i]=update(tr[i-1],1,m,a[i]);
}
while(q--){
scanf("%d%d",&l,&r);
int len=r-l+1;
ll ans=-1;
while(len>=3){
int aa=b[kth(tr[l-1],tr[r],1,m,len)];
int bb=b[kth(tr[l-1],tr[r],1,m,len-1)];
int cc=b[kth(tr[l-1],tr[r],1,m,len-2)];
if(aa>=bb+cc){
len--;
continue;
}
ans=1ll*aa+1ll*bb+1ll*cc;
break;
}
printf("%lld\n",ans);
}
}
return 0;
}