2023.9.10模拟赛

教师节快乐

回归正题,模拟赛链接

T1

给定长度为 \(n\) 的序列 \(a\),定义区间 \([l,r]\) 的权值为 \(a_l\times a_r\)\(q\) 次询问给定 \(l\)\(r\),求所有左端点 \(i\) 和右端点 \(j\) 满足 \(l\le i\le j\le r\) 的区间的权值和。

前缀和解决即可,维护 \(a_i\) 的前缀和 \(sum1\)\(a_i\times sum1_{i-1}\) 的前缀和 \(sum2\),答案为

\[sum1_r\times(sum1_r-sum1_{l-1})-(sum2_r-sum2_{l-1}) \]

维护的方式有很多种,但是我用的线段树。

因此这里放上我考场上的线段树代码
#include<iostream>
using namespace std;
const int N=1e5+10,M=4e5+10;
typedef long long ll;
int n,m,a[N];
ll sum[N];
struct Segment_Tree
{
    ll sum,mul;
    #define ls u<<1
    #define rs u<<1|1
    #define mid (l+r>>1)
}t[M];
void pushup(int u,int l,int r)
{
    t[u].mul=t[ls].mul+t[rs].mul+t[ls].sum*t[rs].sum;
    t[u].sum=t[ls].sum+t[rs].sum;
}
void build(int u,int l,int r)
{
    if(l==r)return t[u].sum=a[l],void();
    build(ls,l,mid),build(rs,mid+1,r);
    pushup(u,l,r);
}
Segment_Tree query(int u,int l,int r,int pl,int pr)
{
    if(pl<=l&&r<=pr)return t[u];
    if(pl<=mid&&pr>mid)
    {
        auto t1=query(ls,l,mid,pl,pr),t2=query(rs,mid+1,r,pl,pr);
        return {t1.sum+t2.sum,t1.mul+t2.mul+t1.sum*t2.sum};
    }
    if(pl<=mid)return query(ls,l,mid,pl,pr);
    if(pl>mid)return query(rs,mid+1,r,pl,pr);
}
int main()
{
    freopen("summation.in","r",stdin);
    freopen("summation.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        sum[i]=sum[i-1]+a[i]*a[i];
    }
    build(1,1,n);
    while(m--)
    {
        int l,r;
        cin>>l>>r;
        auto t=query(1,1,n,l,r);
        cout<<t.mul+sum[r]-sum[l-1]<<"\n";
    }
    return 0;
}

T2

给定正整数 \(n\),非负整数 \(k\) 和一个数 \(m\),你每次可以进行两种操作之一:

  1. \(n\) 变为 \(n+1\)
  2. \(n\) 变为 \(2n+k\)

求令 \(n=m\) 最少次数。

之前做过一道类似的题。当时那道题用搜索的方式喜提 TLE,实际上加上记忆化搜索和贪心技巧就能过。这题是那道题的弱化版。

正着推感觉不太优,就可以考虑倒着推。

实际上,可以直接倒着考虑让 \(m\) 变小直到 \(m=n\),即将两个操作变换成:

  1. \(m\) 变为 \(m-1\)
  2. \(m\) 变为 \((m-k)/2\),前提是 \(m-k\) 是偶数。

转换后可以直接贪心做,如果当前能进行操作二就进行操作二,否则只能进行操作一。

代码就很简单了
#include<iostream>
using namespace std;
int n,m,k;
int main()
{
    freopen("optimality.in","r",stdin);
    freopen("optimality.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int T;
    cin>>T;
    while(T--)
    {
        cin>>n>>k>>m;
        int res=0;
        while(n!=m)
            if((m-k)%2==0)
                if((m-k)/2>=n)m=(m-k)/2,res++;
                else
                {
                    res+=m-n;
                    break;
                }
            else if(m>n)m--,res++;
        cout<<res<<'\n';
    }
    return 0;
}

T3

给定一个 \(n\) 个点,\(n-1\) 条边,每条边的边权唯一。你需要给边定向,保证所有路径的权值单调下降,输出合法方案的数量。

一个性质:对于连接节点 \(u\) 的所有边 \(e_u\),给其中一条边 \(e_{u,1}\) 定向为指向节点 \(u\)。设其边权为 \(w_{u,1}\),那么对于节点 \(u\) 的所有比 \(w_{u,1}\) 大的边 \(e_{u,i}\),一定也指向当前节点 \(u\)

有了这个性质,我们就可以考虑考虑树形 DP,遍历整个图。

具体来说,从根节点开始,先给每个节点的所有出边进行排序。我们简称父亲到当前点的边父亲边,这样就可以将节点分为两类:

  1. 边权比父亲到当前点小的所有边,我们称其为小边
  2. 边权比父亲到当前点大的所有边,我们称其为大边

对于小边,可以一一定向统计方案,对于大边则需要被动的根据父亲边和小边的结果决定方案数。具体而言:

  • 如果某个小边指向父亲的方向,那么所有比它大的边都需要指向父亲方向,同时父亲边需要指向儿子方向。
  • 如果所有的小边都指向儿子方向,父亲边指向儿子方向,那么所有大边都只能指向父亲方向。
  • 如果所有小边都指向儿子方向,父亲边指向父亲方向,那么如果某个大边指向父亲,则所有比它大的边都需要指向父亲。

我们设状态 \(f_{u,0/1}\) 表示当前父亲边 \(e_u\) 指向儿子/父亲的方案数。
在每次转移的过程中,从小到大枚举哪一条边是第一个指向父亲的边统计答案即可,在维护的过程中,可以用前缀积优化。

代码在这里
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int N=3e5+10,mod=998244353;
typedef long long ll;
int n,f[N][2];//f[u][0]表示向子节点指,f[u][1]表示向父节点指
typedef pair<int,int>PII;
#define x first
#define y second
vector<PII>edge[N];
int pow(int a,int b)
{
    int res=1;
    while(b)
    {
        if(b&1)res=(ll)res*a%mod;
        a=(ll)a*a%mod;
        b>>=1;
    }
    return res;
}
void dfs(int u,int fa)
{
    int din=1,dout=1,now=0;
    sort(edge[u].begin(),edge[u].end());
    for(PII t:edge[u])
    {
        int j=t.y;
        if(j==fa)continue;
        dfs(j,u);
        din=(ll)f[j][1]*din%mod;
    }
    f[u][0]=din,f[u][1]=0;
    for(PII t:edge[u])
    {
        int j=t.y;
        if(j==fa)
        {
            now=1;
            f[u][now]=(f[u][now]+(ll)dout*din%mod)%mod;
            continue;
        }
        dout=(ll)dout*f[j][0]%mod;
        din=(ll)din*pow(f[j][1],mod-2)%mod;
        f[u][now]=(f[u][now]+(ll)dout*din%mod)%mod;
    }
}
int main()
{
    freopen("direction.in","r",stdin);
    freopen("direction.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1,a,b;i<n;i++)
    {
        cin>>a>>b;
        edge[a].push_back({i,b});
        edge[b].push_back({i,a});
    }
    dfs(1,0);
    cout<<f[1][0];
    return 0;
}

T4

给定一个长度为 \(n\) 的序列 \(a\),保证 \(0\le a_i\le 2\)\(q\) 次修改,每次形如将在区间 \([l,r]\) 内的所有 \(a_i\) 变为 \((a_i+1)\mod 3\)。每次修改后询问多少区间 \([L,R]\) 同时包含三种元素。

双指针能拿 \(60\) 分,但是正解是线段树。
双指针用前缀和优化,每次维护从当前点开始到哪个点满足条件,最后 \(O(n)\) 统计答案即可。

代码放在这里
#include<iostream>
#include<cstring>
using namespace std;
const int N=5e5+10,inf=0x3f3f3f3f;
typedef long long ll;
int n,m,a[N],cnt[3],ck[N];
bool flag[3];
int main()
{
    freopen("maintaining.in","r",stdin);
    freopen("maintaining.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>a[i];
    int l=1,r=0;
    ll res=0;
    memset(ck,0x3f,sizeof ck);
    while(1)
    {
        while((!cnt[0]||!cnt[1]||!cnt[2])&&r+1<=n)cnt[a[++r]]++;
        while(cnt[0]&&cnt[1]&&cnt[2])
        {
            cnt[a[l++]]--;
            ck[l-1]=r;
        }
        if(r==n)break;
    }
    for(int i=1;i<=n;i++)
        if(ck[i]!=inf)res+=n-ck[i]+1;
    cout<<res<<'\n';
    while(m--)
    {
        cin>>l>>r;
        for(int i=l;i<=r;i++)a[i]=(a[i]+1)%3;
        l=1,r=0;
        cnt[0]=cnt[1]=cnt[2]=res=0;
        memset(ck,0x3f,sizeof ck);
        while(1)
        {
            while((!cnt[0]||!cnt[1]||!cnt[2])&&r+1<=n)cnt[a[++r]]++;
            while(cnt[0]&&cnt[1]&&cnt[2])
            {
                cnt[a[l++]]--;
                ck[l-1]=r;
            }
            if(r==n)break;
        }
        for(int i=1;i<=n;i++)
            if(ck[i]!=inf)res+=n-ck[i]+1;
        cout<<res<<'\n';
    }
    return 0;
}

线段树维护的信息有:每个区间最左边和最右边的 \(0,1,2\) 的位置,最长的相同数字前缀和相同数字后缀,区间的方案数。

代码在这里
#include<iostream>
#include<algorithm>
using namespace std;
const int N=5e5+10,M=N<<2;
typedef long long ll;
int n,m,a[N];
struct Segment_Tree
{
    #define ls u<<1
    #define rs u<<1|1
    #define mid (l+r>>1)
    int tag,pre[3],suf[3],tl[3],tr[3];
    ll sum;
}t[M];
ll get(int sum)
{
    return(ll)sum*(sum+1)/2;
}
void pushup(int u,int l,int r)
{
    t[u].sum=t[ls].sum+t[rs].sum;
    for(int i=0;i<3;i++)
    {
        t[u].sum+=get(t[ls].suf[i]+t[rs].pre[i])-get(t[ls].suf[i])-get(t[rs].pre[i]);
        t[u].pre[i]=t[ls].pre[i]==mid-l+1?mid-l+1+t[rs].pre[i]:t[ls].pre[i];
        t[u].suf[i]=t[rs].suf[i]==r-mid?r-mid+t[ls].suf[i]:t[rs].suf[i];
        if(!t[ls].tl[i]||!t[rs].tl[i])
        {
            t[u].tl[i]=t[ls].tl[i]+t[rs].tl[i];
            t[u].tr[i]=t[ls].tr[i]+t[rs].tr[i];
        }
        else 
        {
            t[u].sum-=get(t[rs].tl[i]-t[ls].tr[i]-1);
            t[u].tl[i]=t[ls].tl[i];
            t[u].tr[i]=t[rs].tr[i];
        }
    }
}
void add_tag(int u,int tag)
{
    tag%=3;
    if(tag)
    {
        t[u].tag=(t[u].tag+tag)%3;
        rotate(t[u].tl,t[u].tl+3-tag,t[u].tl+3);
        rotate(t[u].tr,t[u].tr+3-tag,t[u].tr+3);
        rotate(t[u].pre,t[u].pre+3-tag,t[u].pre+3);
        rotate(t[u].suf,t[u].suf+3-tag,t[u].suf+3);
    }
}
void pushdown(int u,int l,int r)
{
    if(t[u].tag)
    {
        add_tag(ls,t[u].tag);
        add_tag(rs,t[u].tag);
        t[u].tag=0;
    }
}
void build(int u,int l,int r)
{
    if(l==r)
    {
        t[u].tl[a[l]]=t[u].tr[a[r]]=l;
        t[u].pre[a[l]]=t[u].suf[a[l]]=t[u].sum=1;
        return;
    }
    build(ls,l,mid),build(rs,mid+1,r);
    pushup(u,l,r);
}
void update(int u,int l,int r,int pl,int pr)
{
    if(pl<=l&&r<=pr)
    {
        add_tag(u,1);
        return;
    }
    pushdown(u,l,r);
    if(pl<=mid)update(ls,l,mid,pl,pr);
    if(pr>mid)update(rs,mid+1,r,pl,pr);
    pushup(u,l,r);
}
ll query(int u,int l,int r,int pl,int pr)
{
    ll res=get(pr-pl+1);
    res+=t[1].sum;
    for(int i=0;i<3;i++)
        if(!t[1].tl[i])res-=get(pr-pl+1);
        else res-=get(t[1].tl[i]-1)+get(n-t[1].tr[i]);
    return res;
}
int main()
{
    freopen("maintaining.in","r",stdin);
    freopen("maintaining.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>a[i];
    build(1,1,n);
    cout<<query(1,1,n,1,n)<<'\n';
    while(m--)
    {
        int l,r;
        cin>>l>>r;
        update(1,1,n,l,r);
        cout<<query(1,1,n,1,n)<<"\n";
    }
    return 0;
}
posted @ 2023-09-10 22:17  week_end  阅读(11)  评论(0)    收藏  举报