LG 杂题

P10868 [HBCPC2024] Points on the Number Axis B

给你一个不降序列 \(a\),求每次随机将相邻的两个数替换成他们的平均数,最后剩下的一个数的期望。

\(n\le 10^6,a_i<998244353\)

拜谢巨佬 xiezheyuan

非常巧的一个题,可以说和 BBQ Hard 思路相反。

对于一个数 \(a_i\),对于一种情况 \(t\),它对答案贡献的系数为 \(2^{-k_t}\),其中 \(k_t\) 是这种情况下它被选取的次数。由于期望可加,所以我们可以对于每个 \(a_i\),算出它对答案的贡献系数和 \(\sum\limits_{t\in S}2^{-k_t}\)。设 \(f(i,j)\) 表示当前(合并了)前面有 \(i\) 个数(相当于现在在 \(i+1\)),后面有 \(j\) 个数的贡献系数。则答案为 \(\sum\limits_{i=1}^n f(i,n-i-1)\)

考虑递推,若合并的数在前/后面 \(i/j\) 个数中(有 \(i-1/j-1\) 种选法),则当前数的贡献系数不变,若选定了当前数,则它可以和前/后面相邻的数合并,且系数 \(\times \frac{1}{2}\)

综上,递推式为

\[f(i,j)=\frac{i-1}{i+j}f(i-1,j)+\frac{1}{2}\frac{1}{i+j}(f(i-1,j)+f(i,j-1))+\frac{j-1}{i+j}f(i,j-1) \]

整理一下

\[f(i,j)=\frac{i-\frac{1}{2}}{i+j}f(i-1,j)+\frac{j-\frac{1}{2}}{i+j}f(i,j-1) \]

两边乘 \((i+j)!\)

\[(i+j)!f(i,j)=(i-\frac{1}{2})((i-1)+j)!f(i-1,j)+(j-\frac{1}{2})(i+(j-1))!f(i,j-1) \]

这个形式很漂亮,不妨设 \(g(i,j)=(i+j)!f(i,j)\),则

\[g(i,j)=(i-\frac{1}{2})g(i-1,j)+(j-\frac{1}{2})g(i,j-1) \]

有 BBQ Hard 那味了。联想一下,这等价于从 \((0,0)\) 走到 \((i,j)\) 的方案数的路径权值(由于不带95:56权时为 \(1\),可以看做是把每边边权乘起来)之和,限制向右或上走,且横向边权为 \((i-\frac{1}{2})\),纵向边权为 \((j-\frac{1}{2})\)。可以发现每种方案的路径权值固定为 \(\prod\limits_{k=1}^{i}(i-\frac{1}{2})\prod\limits_{k=1}^{j}(j-\frac{1}{2})\),而方案数为 \(\binom{i+j}{i}\),所以上式可化简为

\[g(i,j)=\binom{i+j}{i}\prod\limits_{k=1}^{i}(i-\frac{1}{2})\prod\limits_{k=1}^{j}(j-\frac{1}{2}) \]

预处理组合数和积即可。时间复杂度 \(O(n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
const int mod=998244353;
const int maxn=1e6+3;
using namespace std;
const int N=2003;
int n,a[maxn];
int inv[maxn],fac[maxn],invf[maxn],g[maxn],fack[maxn];
int C(int a,int b){
    if(a<b) return 0;
    return fac[a]*invf[b]%mod*invf[a-b]%mod;
}
int qpow(int a,int b){
    int res=1;
    for(;b;a=a*a%mod,b>>=1) if(b&1) res=res*a%mod;
    return res; 
}
signed main(){
    cin>>n;
    fac[0]=fac[1]=inv[1]=1;
    for(int i=2;i<=n;i++){
        inv[i]=(mod-mod/i)*inv[mod%i]%mod;
        fac[i]=fac[i-1]*i%mod;
    }
    invf[n]=qpow(fac[n],mod-2);
    for(int i=n-1;~i;i--) 
        invf[i]=invf[i+1]*(i+1)%mod;
    fack[0]=1; fack[1]=inv[2];
    for(int i=2;i<=n;i++)
        fack[i]=fack[i-1]*(i-inv[2]+mod)%mod;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    int ans=0;
    for(int i=1,j=n;i<=n;i++,j--){
        g[i]=a[i]*C(n-1,i-1)%mod*fack[i-1]%mod*fack[j-1]%mod*invf[n-1]%mod;
        ans=(ans+g[i])%mod;
    }
    cout<<ans;
    return 0;
}

P10863 [HBCPC2024] Enchanted

给你一个附魔书序列 \(a\),支持以下操作:

  • 求合并 \([l,r]\) 所有附魔书可以得到的最高等级(实际上不做任何修改);
  • 合并 \([l,r]\) 所有附魔书,再与等级为 \(k\) 的附魔书合并,求附魔花费(实际上不做任何修改);
  • \(a_x\) 的等级修改为 \(k\)
  • 返回第 \(t\) 次操作之后。

We could merge two books with the same level \(l\) into one new book(the older two will disappear). The level of the new book is \(l+1\) and the merger costs \(2^{l+1}\).

\(a_i\le 30,n,m\le 10^6\)

附魔书的合并很像二进制相加,不妨设附魔书 \(i\) 的权值为 \(2^{a_i-1}\),则合并相当于加法。则原操作为:

  • 求区间和的二进制最高位。直接维护区间和即可;
  • 求区间和加上 \(k\) 与原来相比的二进制变化位数和。设区间和为 \(t\),则答案为 \(2\times ((t+k)\oplus t\oplus k)\)
  • 其余操作可持久化即可。

时间复杂度 \(O(n\log n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define lc tree[pos].ls
#define rc tree[pos].rs
using namespace std;
const int maxn=1e6+3;
const int mod1=19260817;
const int mod2=1e9+7;
struct T{
	int sum,ls,rs;
}tree[maxn<<5];
int tim,cnt;
int n,m,a[maxn],A,P,Q;
int root[maxn];
int rnd(){
    return A=(7*A+13)%mod1;
}
void pushup(int pos){
    tree[pos].sum=tree[lc].sum+tree[rc].sum;
}
void update(int &pos,int l,int r){
	if(!pos) pos=++cnt;
	if(l==r){
		tree[pos].sum=a[l];
		return;
	}
	int mid=(l+r)>>1;
	update(lc,l,mid);
	update(rc,mid+1,r);
    pushup(pos);
}
void modify(int rt,int &pos,int l,int r,int x,int v){
    if(!pos) pos=++cnt;      // 新建节点,加 & 保证不会访问到空节点并直接存在儿子中
    if(l==r){
        tree[pos].sum=v;
        return;
    }
    int mid=(l+r)>>1;
    if(x<=mid){
        tree[pos].ls=++cnt;    // 新建左儿子
        tree[pos].rs=tree[rt].rs;
        tree[lc]=tree[tree[rt].ls];// 继承右儿子的所有信息
        modify(tree[rt].ls,lc,l,mid,x,v);
    }else{
        tree[pos].rs=++cnt;    // 同理
        tree[pos].ls=tree[rt].ls;
        tree[rc]=tree[tree[rt].rs];
        modify(tree[rt].rs,rc,mid+1,r,x,v);
    }
    pushup(pos);
}
int query(int pos,int l,int r,int L,int R){
	if(L<=l&&r<=R) return tree[pos].sum;
	int mid=(l+r)>>1,sum=0;
	if(L<=mid) sum+=query(lc,l,mid,L,R);
	if(mid+1<=R) sum+=query(rc,mid+1,r,L,R);
    return sum;
}
int highbit(int x){
    int top=0;
    while(x){
        x/=2;
        top++;
    }
    return top;
}
signed main(){
	ios::sync_with_stdio(0);
	cin>>n>>m>>A>>P>>Q; 
	for(int i=1;i<=n;i++){
		a[i]=1<<(rnd()%Q);
	}
	update(root[0],1,n);
	for(int i=1,op,l,r,L,R,k;i<=m;i++){
		op=rnd()%P+1;
		if(op==1){
			L=rnd()%n+1;
            R=rnd()%n+1;
            l=min(L,R);
            r=max(L,R);
            root[i]=root[i-1];
            int t=query(root[i],1,n,l,r);
            cout<<highbit(t)%mod2<<'\n';
		}else if(op==2){
			L=rnd()%n+1;
            R=rnd()%n+1;
            l=min(L,R);
            r=max(L,R);
            k=1<<(rnd()%Q);
            root[i]=root[i-1];
			int t=query(root[i],1,n,l,r);
            cout<<((t+k)^t^k)*2%mod2<<'\n';
		}else if(op==3){
			L=rnd()%n+1;
            k=1<<(rnd()%Q);
            modify(root[i-1],root[i],1,n,L,k);
        }else{
            k=rnd()%i;
            root[i]=root[k];
        }
	}
	return 0;
}

P11162 「BalkanOI 2023 Day1」Car Race

给你一棵树,有些节点上有车。每个单位时间车向根移动一个单位,当一个根以外的节点同时有多辆车,这些车就爆了无法完成比赛。判断每辆车能否到根,并求所有可到根的车的时间。

\(n\le 10^6\)

对于一个 1 的真子树内,如果存在某个深度有大于一辆车,则它们会爆。考虑 DSU on tree,记 \(s_{dep}\) 表示当前深度为 \(dep\) 的车的编号,先将重儿子的答案计入,再跑轻儿子,每步判断是否合法,不合法直接更新对于节点答案并标记该深度会爆,最后消去轻儿子的影响即可。时间复杂度 \(O(n\log n)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+3;
int n,fa[maxn],dep[maxn],idx[maxn],car[maxn],ans[maxn],son[maxn],siz[maxn],dfn[maxn],rdfn[maxn],dfncnt;
vector<int>e[maxn];
void dfs(int u){
    dfn[u]=++dfncnt;
    idx[dfncnt]=u;
    dep[u]=dep[fa[u]]+1;
    siz[u]=1;
    for(int v:e[u]){
        if(v!=fa[u]){
            dfs(v);
            siz[u]+=siz[v];
            if(siz[son[u]]<siz[v]) son[u]=v;
        }
    }
    rdfn[u]=dfncnt;
}
int sdep[maxn];
vector<int>br1,br2;
void add(int u){
    for(int i=dfn[u];i<=rdfn[u];i++){
        int v=idx[i];
        if(ans[v]||!car[v]) continue;
        if(sdep[dep[v]]==-1) ans[v]=1;
        else if(sdep[dep[v]]){
            ans[sdep[dep[v]]]=1;
            br1.emplace_back(dep[v]);
            br2.emplace_back(dep[v]);
            ans[v]=1; sdep[dep[v]]=-1;
        }else{
            sdep[dep[v]]=v;
            br1.emplace_back(dep[v]);
        }
    }
}
void dfs1(int u){
    for(int v:e[u]){
        if(v!=fa[u]&&v!=son[u]){
            dfs1(v);
            for(int i:br1) sdep[i]=0;
            br1.clear();
        }
    }
    if(son[u]) dfs1(son[u]);
    if(u==1) return;
    if(car[u])sdep[dep[u]]=u,
    br1.emplace_back(dep[u]);
    for(int v:e[u]){
        if(v!=fa[u]&&v!=son[u]) add(v);
    }
    for(int i:br2) sdep[i]=0;
    br2.clear();
}
signed main(){
    cin>>n;
    for(int i=2;i<=n;i++){
        cin>>fa[i];
        fa[i]++;
        e[i].emplace_back(fa[i]);
        e[fa[i]].emplace_back(i);
    }
    dfs(1);
    for(int i=1;i<=n;i++){
        cin>>car[i];
    }
    dfs1(1);
    for(int i=1;i<=n;i++){
        if(ans[i]||!car[i]) cout<<-1<<' ';
        else cout<<dep[i]-1<<' ';
    }
    return 0;
}

P11157 【MX-X6-T3】さよならワンダーランド

给你一个序列 \(a\),对于每个 \(i\in[1,n]\),求一个 \(j\) 满足:

  • \(1\le i+j\le n\)
  • \(a_i\le j\le a_{i+j}\)

若不存在输出 0。

\(n\le 3\times 10^5,|a_i|\le 10^9\)

\(k=i+j\),则原式为 \(\exists k\in[1,n],a_i+i\le k\le a_k+i\),拆成 \(a_i+i\le k\)\(i\le a_k-k\),将所有 \(a_i+i\le n\) 的数开一个桶存起来,然后取 \(a_k-k\) 的后缀最大值 \(M\),每次判断桶 \(i\) 里的数 \(t\) 是否满足 \(M\ge-t\) 即可。时间复杂度 \(O(n)\),当然也可以线段树维护。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+3;
int n,a[maxn],ans[maxn];
vector<int>v[maxn];
signed main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        ans[i]=-2e9;
        if(a[i]+i>n) continue;
        v[max(1,a[i]+i)].emplace_back(i);
    }
    int pos=0,mx=-2e9;
    for(int i=n;i;i--){
        if(mx<a[i]-i) pos=i,mx=a[i]-i;
        for(int j:v[i]){
            if(mx+j>=0) ans[j]=pos-j;
        }
    }
    for(int i=1;i<=n;i++){
        if(ans[i]==-2e9) cout<<"0\n";
        else cout<<"1 "<<ans[i]<<'\n';
    }
    return 0;
}

P9108 [PA2020] Malowanie płotu

考试原题。同时纪念第一道独立写出来的紫题。

有一个 \(n\)\(m\) 列的木板,对于每一行涂色连续的一段,满足相邻两行涂色部分有相邻,求合法的方案数。

\(n\times m \le 10^7\)

考虑一个显然的 DP,设 \(f(i,L,R)\) 表示第 \(i\) 行涂色 \([L,R]\) 的方案数,则方案数为总方案数减去不交的方案数,则有转移:

\[f(i,L,R)=\sum\limits_{1\le l_0\le r_0\le m} f(i-1,l_0,r_0)-\sum\limits_{1\le l_0\le r_0<L} f(i-1,l_0,r_0)-\sum\limits_{R<l_0\le r_0\le m} f(i-1,l_0,r_0) \]

显然可以滚掉 \(i\) 一维。

\(g(L,R)\)\(f(,1\sim L,1\sim R)\) 的前缀和。则有:

\[g(L,R)=\sum\limits_{1\le l\le r\le m}f(l,r) \]

\[\begin{aligned} f(L,R)&=g(m,m)-g(L-1,L-1)-(g(m,m)-g(m,R)-g(R,m)+g(R,R))\\ &=g(R,m)+g(m,R)-g(R,R)-g(L-1,L-1) \end{aligned}\]

考虑到 \(L\le R\),所以 \(g(m,R)=g(R,R)\),进一步

\[f(L,R)=g(R,m)-g(L-1,L-1) \]

时间复杂度 \(O(m^2n)\)。貌似优化不了了?上 trick——拆维度。

考虑修改 \(f\) 的定义:设 \(f_L(L,len)\)\([L,L+len-1]\) 的方案,则

\[f_L(L,len)=g(L+len-1,m)-g(L-1,L-1) \]

\(len\) 拿出来枚举

\[\begin{aligned} f_L(L)&=\sum\limits_{len=1}^{m-L+1} g(L+len-1,m)-g(L-1,L-1)\\ &=-(m-L+1)g(L-1,L-1)+\sum\limits_{len=1}^{m-L+1} g(L+len-1,m) \end{aligned}\]

同理有

\[\begin{aligned} f_R(R)&=\sum\limits_{len=1}^{R} g(R,m)-g(R-lem+1,R-len+1)\\ &=Rg(R,m)+\sum\limits_{len=1}^{R} g(R-len+1,R-len+1) \end{aligned}\]

\(u(x)\) 表示 \(g(x,m)\)\(v(x)\) 表示 \(g(m,x)=g(x,x)\),则

\[u(x)=\sum\limits_{i=1}^x f_L(i),v(x)=\sum\limits_{i=1}^x f_R(i) \]

\[f_L(L)=-(m-L+1)v(L-1)+\sum\limits_{len=1}^{m-L+1} u(L+len-1) \]

\[f_R(R)=Ru(R)+\sum\limits_{len=1}^{R} v(R-len+1) \]

再设 \(u'(x)=\sum\limits_{i=1}^x u(i),v'(x)=\sum\limits_{i=1}^x v(i)\),则有

\[f_L(L)=-(m-L+1)v(L-1)+u'(m)-u'(L-1) \]

\[f_R(R)=Ru(R)+v'(R-1) \]

大力转移即可,注意取模,最后答案为 \(u(m)/v(m)\)(这两个值是相等的,若不等就说明写挂了)。时空复杂度皆为 \(O(nm)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
const int maxn=1e7+3;
// const int mod=998244353;
using namespace std;
int n,m,mod;
int f[2][maxn],g[2][maxn],uu[maxn],vv[maxn];
signed main(){
	// freopen("true.in","r",stdin);
	// freopen("true.out","w",stdout);
	cin>>n>>m>>mod;
	for(int i=1;i<=m;i++)
		f[0][i]=m-i+1,f[1][i]=i;
	for(int i=1;i<=m;i++){
		g[0][i]=(g[0][i-1]+f[0][i])%mod;
		g[1][i]=(g[1][i-1]+f[1][i])%mod;
		uu[i]=(uu[i-1]+g[0][i])%mod;
		vv[i]=(vv[i-1]+g[1][i])%mod;
	}
	for(int i=2;i<=n;i++){
		for(int L=1;L<=m;L++)
			f[0][L]=((-m+L-1)*g[1][L-1]%mod+uu[m]-uu[L-1]+mod+mod)%mod;
		for(int R=1;R<=m;R++)
			f[1][R]=((R)*g[0][R]-vv[R-1]+mod)%mod;
		for(int i=1;i<=m;i++){
			g[0][i]=(g[0][i-1]+f[0][i])%mod;
			g[1][i]=(g[1][i-1]+f[1][i])%mod;
			uu[i]=(uu[i-1]+g[0][i])%mod;
			vv[i]=(vv[i-1]+g[1][i])%mod;
		}
	}
	cout<<g[0][m];
	return 0;
}

P8590 『JROI-8』这是新历的朝阳,也是旧历的残阳

少女于海边伫立,凝视着落日最后的余晖
“已然过去了呢,旧历的一年......”

给你一个不降序列 \(a\),对于每个 \(m\in [1,k]\),将序列划分成 \(m\) 段,对于第 \(i\) 段将段内的数加上 \(i\),并求序列平方的和。

\(n\le 10^6,k\le 10^7,|a_i|\le 10^7\)

单独计算 \(m=1\) 的答案。对于 \((a_i+1)^2>(a_i+m)^2\) 的数放在第一段,其余放在最后一段一定是最优的。考虑到随着 \(m\) 增大这个分界点逐渐左移,于是一个指针向左走,前缀和统计答案即可。时间复杂度 \(O(n+k)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
const int maxn=1e6+3;
const int mod=998244353;
using namespace std;
int n,k,a[maxn],p1[maxn],p2[maxn],p3[maxn];
signed main(){
    cin>>n>>k;
    int neg=n;
    __int128 ans1=0,ans2=0;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        ans1=(ans1+(a[i]+1)*(a[i]+1)%mod)%mod;
    }
    if(k==1){
        cout<<(long long)ans1;
        return 0;
    }
    for(int i=1;i<=n;i++){
        p1[i]=(p1[i-1]+a[i])%mod;
        p2[i]=(p2[i-1]+a[i]*a[i]%mod)%mod;
        p3[i]=(p3[i-1]+(a[i]+1)*(a[i]+1)%mod)%mod;
    }
    for(int i=2;i<=k;i++){
        while(neg&&abs(a[neg]+1)<abs(a[neg]+i)) neg--;
        int d0=n-neg,tx=d0*i%mod*i%mod;
        tx=(tx+2*i*(p1[n]-p1[neg]+mod)%mod)%mod;
        tx=(tx+p2[n]-p2[neg]+mod)%mod;
        tx=(tx+p3[neg])%mod;
        ans2=(ans2+tx)%mod;
    }
    cout<<(long long)((ans1+ans2)%mod);
    return 0;
}

P7981 [JRKSJ R3] system

给你一个数列 \(p_i\),每次操作同时 \(p_i=p_{p_i}\),求 \(k\) 次操作后的 \(p\) 数列。

\(p_i\le n\le 5\times 10^5,k\le 10^{18}\)

部分分(50 pts):\(p\) 是一个排列。

即为 ABC377E Permute K times 2最新最热

考虑建图,连边 \(i\to p_i\)(本质上就是 \(p_i\to p_{p_i}\)),可以形成若干置换环,而每次操作将会 \(p_i\to p_{p_i}\to p_{p_{p_{p_{i}}}}\to p_{p_{p_{p_{p_{p_{p_{p_{i}}}}}}}}\to \cdots\)\(2^k\)\(p\)),相当于在环上走了 \(2^k\) 步到达的位置,直接上快速幂,对环长取模即可。时间复杂度 \(O(n\log k)\)

100 pts:

同样建图,会形成内向基环树森林。考虑分两步:先跳到环上,再在环上按 50 分做法快速幂。第一步可以用一个类似求 LCA 时用的 \(2^x\) 级祖先的一个倍增数组 \(fa(x,i)\),由于 \(n\le 5\times 10^5\)\(x\) 最多预处理到 \(19\)。对于 \(k\le 19\),直接跳 \(fa(k,i)\) 即为答案;对于 \(k>19\),直接跳 \(2^{20}-1\) 级祖先,一定能跳到环上,然后再在环上走 \(2^k-(2^{20}-1)\) 步即可。时间复杂度为 \(O(n\log n+n\log k)\)

代码实现略有不同。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=5e5+7;
int n,k,rcnt;
int fa[20][maxn],vis[maxn],p[maxn],siz[maxn],ans[maxn],scc[maxn];
vector<int>v[maxn];
int qpow(int a,int b,int mod){
    int res=1;
    for(;b;b>>=1,a=a*a%mod) if(b&1) res=res*a%mod;
    return res;
}
signed main(){
    cin>>n>>k;
    for(int i=1;i<=n;i++){
        cin>>fa[0][i]; p[i]=fa[0][i];
    }
    for(int j=1;j<20;j++)
        for(int i=1;i<=n;i++)
            fa[j][i]=fa[j-1][fa[j-1][i]]; // 处理倍增数组
    for(int i=1;i<=n;i++){ // 找环
        if(vis[i]) continue;
        int j,l;
        for(j=i;!vis[j];j=p[j]) vis[j]=-i;
        if(vis[j]>-i) continue; // 不重复找环
        v[++rcnt].emplace_back(j);
        vis[j]=++siz[rcnt],scc[j]=rcnt;
        for(l=p[j];l!=j;l=p[l]) v[rcnt].emplace_back(l),vis[l]=++siz[rcnt],scc[l]=rcnt;
    } // vis[i]>0 表示 i 在环上的相对顺序
    for(int i=1;i<=n;i++){
        if(k<20){ // 直接跳
            ans[i]=fa[k][i];
        }else{ 
            ans[i]=i;
            int step=0;
            for(int j=19;~j;j--){ // 跳到环上
                ans[i]=fa[j][ans[i]];
                step|=(1<<j);
                if(vis[ans[i]]>0){ // 到环上了
                    int rsiz=siz[scc[ans[i]]];
                    int rk=((vis[ans[i]]-1+qpow(2,k,rsiz)-step%rsiz+rsiz)%rsiz+rsiz)%rsiz;
                    ans[i]=v[scc[ans[i]]][rk];
                    break;
                }
            }
        }
    }
    for(int i=1;i<=n;i++){
        cout<<ans[i]<<' ';
    }
    return 0;
}
posted @ 2024-11-01 15:24  view3937  阅读(1)  评论(0编辑  收藏  举报
Title