AGC040 题解

[AGC040A] ><

题意

给定长为 \(n-1\)<> 字符序列,若 \(s_i=\) <,表示 \(a_i<a_{i+1}\),若 \(s_i=\) >,表示 \(a_i>a_{i+1}\) 。求长为 \(n\) 的序列 \(a_i\) 的和的最小值。

\(n\le 5\times 10^5\)

idea

简单贪心,肯定是尽可能的从 \(0\) 开始填。考虑对于 >< 的子串可以在中间填 \(0\),然后向左向右拓展到 <> ,每个连续的 <<<>>> 只会被更新一次,而 <> 会被左右更新,这时候需要取最大值。

总结

  • 重要观察:>< 位置填 \(0\)

Code

#include<bits/stdc++.h>
#define ll long long
#define N 500005
#define endl "\n" 
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;

ll n;
string s;
ll val[N],cnt[N];
int main(){
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    cin>>s;
    n=s.size(),s=" "+s+" ";
    ll cnt=0;
    for(int i=1;i<=n+1;i++){
        val[i]=cnt++;
        if(s[i]=='>'){
            cnt=0;
            continue;
        }
    }
    cnt=0;
    for(int i=n+1;i>=1;i--){
        val[i]=max(val[i],cnt++);
        if(s[i-1]=='<'){
            cnt=0;
            continue;
        }
    }
    ll res=0;
    for(int i=1;i<=n+1;i++)res+=val[i];
    cout<<res;
    return 0;
}

[AGC040B] Two Contests

题意

\(n\) 个区间,需要分成两组,每组非空,定义每组的权值为其中所有区间的交集的长度,最大化两个组的权值和。

\(n\le 10^5\)

idea

因为取交集,所以放的区间越多权值越小,进一步观察到交集的长度与 \(L=\max\limits_{i\in S} l_i\)\(R=\min\limits_{i\in S} r_i\) 有关。

对全局 \(L,R\) 分析;

\(L,R\) 所对应区间同组:该组区间权值为 \(\max\{R-L+1,0\}\),考虑最大化另一组权值,即单独放长度最长的区间,总权值:$$res=\max{R-L+1,0}+\max\limits_{i=1}^n \left{r_i-l_i+1\right}$$

\(L,R\) 对应区间不同组:设 \(L\) 位于 \(S\)\(R\) 位于 \(T\),于是有:$$res=\max\left{\min\limits_{i\in S}{r_i-L+1,0}+\min\limits_{i\in T}{R-l_i+1,0}\right}$$

于是可以设 \(a_i=\max\{r_i-L+1,0\}\)\(b_i=\max\{R-l_i+1,0\}\)

答案变为 \(res=\max\{\min_{i\in S} a_i+\min_{i\in T}b_i\}\)

\(a_i\) 为关键词降序排序,注意到其实是取一段前缀 \([1,i]\)\(a_i\),价值是 \(a_i\),和一段后缀 \([i+1,n]\)\(b_j\),价值是后缀 \(b_j\) 最小值。

总结

  • 重要观察:\(L,R\) 的放置对答案的影响。

Code

#include<bits/stdc++.h>
#define ll long long
#define N 200005
#define endl "\n" 
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;

ll n;
pair<ll,ll>q[N];
pair<ll,ll>f[N];

ll lmi[N];
int main(){
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    cin>>n;
    ll res=0,le=0,ri=inf;
    for(int i=1;i<=n;i++){
        cin>>q[i].fi>>q[i].se;
        le=max(le,q[i].fi);
        ri=min(ri,q[i].se);
        res=max(res,q[i].se-q[i].fi+1);
    }
    res+=max(ri-le+1,0ll);
    for(int i=1;i<=n;i++)f[i]={max(ri-q[i].fi+1,0ll),max(q[i].se-le+1,0ll)};
    sort(f+1,f+n+1);
    lmi[0]=inf;
    for(int i=1;i<=n;i++)lmi[i]=min(lmi[i-1],f[i].se);
    for(int i=2;i<=n;i++)res=max(res,f[i].fi+lmi[i-1]);
    cout<<res<<endl;
    return 0;
}

[AGC040C] Neither AB nor BA

题意

给出一个大于0的偶数 \(n\)

请找出长度为 \(n\) ,由'A','B','C'这三个字母组成且可以由下列规则把其变为空串的字符串 \(s\) 的数量。

  • 不断选择 \(s\) 中任意除'AB'和'BA'外的长度为 \(2\) 的子串并删除。

将结果对 \(998244353\) 取模。

idea

可以删空不好计算,考虑容斥,全部的数量减去无法删空的数量。

一个字符串无法删空多半是有交替的 AB 频繁出现,考虑对 AB 计数。

Sol

考虑把 B 变成 A,条件变成 AA 不能删去。

于是我们可以枚举放了 \(i\)A,注意到如果 \(2i\le n\) ,我们总是可以删除干净,而 \(2i>n\) 总会导致 A 相接从而无法删除。剩下的 \({n-i}\) 的位置可以任意填 BC

考虑对于一串 AAAAA 有两种对应方式: ABABABABAB,于是不能删部分的答案要乘二。

答案为:

\[res=3^n-2\sum\limits_{i=n/2+1}^{n}\binom{n}{i}2^{n-i} \]

总结

  • 重要观察:把 B 变成 A,两个字符变成一个字符。

Code

#include<bits/stdc++.h>
#define ll long long
#define N 10000005
#define endl "\n" 
#define fi first
#define se second
using namespace std;
const ll mod=998244353;
const ll inf=1e18;
const double eps=1e-6;
ll n;
namespace math_permutation{
    ll fpow(ll x,ll y){
        ll res=1;
        while(y){
            if(y&1)res=res*x%mod;
            y>>=1,x=x*x%mod;
        }
        return res;
    }
    ll fac[N],ifac[N],inv[N];
    void work(ll r){
        fac[0]=inv[1]=1;
        for(int i=1;i<=r;i++)fac[i]=fac[i-1]*i%mod;
        ifac[r]=fpow(fac[r],mod-2);
        for(int i=r;i>0;i--){
            ifac[i-1]=ifac[i]*i%mod;
            inv[i]=fac[i-1]*ifac[i]%mod;
        }
    }
    ll C(ll n,ll m){
        if(n<0||n<m)return 0;
        return fac[n]*ifac[m]%mod*ifac[n-m]%mod;
    }
}using namespace math_permutation;
int main(){
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    cin>>n;
    work(N-5);
    ll res=0;
    ll m=n/2;
    for(int i=m+1;i<=n;i++)res=(res+2ll*C(n,i)%mod*fpow(2,n-i)%mod)%mod;
    cout<<(fpow(3,n)%mod-res%mod+mod+mod)%mod;
    return 0;
}

[AGC040D] Balance Beam

题意

给定 \(n\) 个长度为 \(1\) 的石板,Alice 通过第 \(i\) 个石板时的速度为 \(\frac{1}{A_i}\),Bob 通过第 \(i\) 个石板时的速度为 \(\frac{1}{B_i}\)

现在他们进行这样的一个游戏:

  • Alice 以任意顺序排列这 \(n\) 个石板,并构成一个大石板,然后他站在这个大石板的最左边往右跑。
  • Bob 在这长度为 \(n\) 的大石板上均匀随机选择一个点(注意不一定是整点)然后从这个点开始往右跑。
  • 注意两者同时出发,如果在途中 Alice 抓到了 Bob ,则 Alice 获胜。

求 Alice 获胜的最大概率,答案以真分数表示。

保证 \(1\le n\le 10^5,1\le A_i,B_i\le 10^9\)

idea

对于一个确定的排列方式,Bob 总是在一段前缀起跑时会被抓住,设在 \([0,x)\) 出发会被抓,答案是 \(\frac{x}{n}\)

Sol

神仙题。

考虑以位移 \(x\) 为横轴,时间 \(t\) 为纵轴建立平面直角坐标系,画出两人的折线。

注意到两条折线单调递增,能追到的充要条件是两折线有交,考虑改变起点的过程,相当于把 Bob 的折线竖着往下移。

我们设折线下移时最后一次交于 \(x=p\) ,那么此时对应的 Bob 折线的横轴截距即为答案。

考虑最大化这个截距,注意到两人相遇后,其实可以看作 Alice 抓着 Bob 走完了全程。于是对于 \(x>p\),应该取 Alice 的曲线,由于 \(x<p\) 没有抓到,应该取 Bob 曲线,这样我们就把两条折线变成了一条。

注意到这条直线最终一定位于 \((n,\sum\limits_{i=1}^n a_i)\) ,我们要最大化截距,就要最大化每条线的斜率,使得零点与 \(x=n\) 尽可能近。考虑设 \(c_i=\max (a_i,b_i)\)。那么我们把所有 \(c_i=a_i\) 的线段的起点和 \(c_i=b_i\) 的线段的终点放到放到 \(p\) 位置上,对于每一侧贪心的选取最大值。

但是这样无法确定与 \(x\) 轴交于何处,注意到与 \(x\) 相交线段只有一条,考虑枚举这条线段,然后贪心的选取尽可能大的 \(c_i\) 让其到达 \(m=\sum\limits_{i=1}^n a_i\),具体需要的线段数量可以二分求出,计算贡献是简单的一次函数知识。

考虑评估上界能否取得,注意到如果按 \(a_i-b_i\) 从小到大从左到右排放石板,总是会产生交点,所以可以取得上界。

总结

  • 重要观察:转化折线图,并且注意到交点前后可以变成一条折线。

Code

#include<bits/stdc++.h>
#define ll long long
#define N 200005
#define endl "\n" 
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;

struct divi{
    ll x=0,y=1;
    bool friend operator<(const divi &a,const divi &b){
        return (__int128)a.x*b.y<(__int128)b.x*a.y;
    }
};
ll n,m;
struct wood{
    ll a,b,c;
     bool friend operator<(const wood  &x,const wood &y){
        return x.c<y.c;
    }
}p[N];

ll sum[N];
ll h[N];
bool check(ll i,ll x){
    ll val=h[x];
    if(x<=i)val-=p[i].c;
    if(m-val-p[i].b>0)return 0;
    return 1;
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>p[i].a>>p[i].b;
        m+=p[i].a;
        p[i].c=max(p[i].a,p[i].b);
    }
    sort(p+1,p+n+1);
    for(int i=n;i>=0;i--)h[i]=h[i+1]+p[i].c;
    divi res={0,1};
    for(int i=1;i<=n;i++){
        ll l=0,r=n;
        while(l<r){
            ll mid=(l+r+1)>>1;
            if(check(i,mid))l=mid;
            else r=mid-1;
        }
        if(l==0)continue;
        ll val=h[l]+p[i].b-m;
        if(l<=i)val-=p[i].c;
        divi now={val,p[i].b};
        ll d=__gcd(now.x,now.y);
        now.x/=d,now.y/=d;
        if(now.x>now.y)continue;
        now.x+=(now.y)*(l-1-(i<l));
        d=__gcd(now.x,now.y);
        now.x/=d,now.y/=d;
        res=max(res,now);
    }
    if(res.x==0){
        cout<<0<<" "<<1<<endl;
        return 0;
    }
    ll d=__gcd(res.x,n);
    cout<<res.x/d<<" "<<res.y*(n/d);
    return 0;
}

[AGC040E] Prefix Suffix Addition

题意

长度为 \(n\) 的全是 \(0\) 的序列,每次可以给一段前缀或后缀加上一段对应的不下降子序列,最小化操作次数使序列变成给定序列。

\(n\le 10^5,a_i\le 10^9\)

idea

p_b_p_b 上课讲过。

Sol

考虑如果只有一种操作可以直接贪心,即 \(\sum\limits_{i=1}^{n-1}[a_i>a_{i+1}]\)

所以可以考虑把 \(a_i=b_i+c_i\) ,一个前缀加,一个后缀加。

于是设 \(f_{i,j}\) 表示第 \(i\) 位前缀加 \(b_i\) 的最小代价,那么后缀即加 \(a_i-b_i\) 这样便分离的贡献,转移方程较为简单:

\[f_{i,j}=\min\limits_{k=0}^{a_{i-1}}\left\{ f_{i-1,k}+[k>j]+[a_{i-1}-k>a_{i}-j]\right\} \]

转移是 \(O(m)\) 的,考虑优化。

注意到随着 \(j\) 的增大,\(f_i,j\) 单调不增,而 \(f_{i-1,0}\)\(f_{i-1,a_{i-1}}\) 的变化最多只有 \(2\),所以 \(f_{i,j}\) 的取值最多三种,可以通过二分确定每种取值的区间。维护 \(i-1\) 每种取值区间即可做到 \(O(n)\)

总结

  • 重要观察:\(f_{i,j}\) 单调不降且取值最多只有三种。

Code

#include<bits/stdc++.h>
#define ll long long
#define N 200005
#define endl "\n" 
#define fi first
#define se second
using namespace std;
const ll mod=1e9+7;
const ll inf=1e18;
const double eps=1e-6;

ll n,a[N];
struct qwq{
    ll l,r,x;
};
vector<qwq>v[N];
ll calc(ll i,ll val){
    ll res=inf;
    for(auto [l,r,x]:v[i-1]){
        res=min(res,x+(l>val)+(a[i-1]-l<a[i]-val));
    }
    return res;
}
int main(){
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    v[0].push_back({0,0,0});
    for(int i=1;i<=n+1;i++){
        for(int x=0;x<=a[i];x++){
            ll l=x,r=a[i],val=calc(i,x);
            while(l<r){
                ll mid=(l+r+1)>>1;
                if(calc(i,mid)==val)l=mid;
                else r=mid-1;
            }
            v[i].push_back({x,l,val});
            x=l;
        }
    }
    cout<<v[n+1].back().x<<endl;
    return 0;
}

[AGC040F] Two Pieces

题意

有两个棋子初始点都在坐标 \(0\) ,两个棋子之间没有区别,总共要进行 \(n\) 次操作,每次操作是如下操作其中之一:

1.选择一个棋子向前移动一步。

2.选择位置较后的那个棋子,直接移动到位置较前的那个棋子的位置。

\(n\) 次操作后两个棋子分别在位置 \(A,B\) 的方案数,对 \(998244353\) 取模,两种方案是相同的,当且仅当两个棋子在每一步的坐标都是相同的(注意不是每一步的操作都相同)。

\(A+B\le n\)

idea

只会朴素 dp 。

注意到没有操作 \(2\) 其实就是折线图不能越过 \(y=x\)

Sol

又一道神仙题。

考虑钦定 \(A\ge B\),最终走到 \((A,B)\)

更改三个操作:

  1. \(a\) 走:\(x=x+1\)
  2. \(b\) 走:\(y=y+1\)
  3. \(b\) 移到 \(a\)\(y=x\)

注意到 \(y=x-1\) 时操作 \(2,3\) 等效,考虑操作 \(2\) 不能越过 \(y=x-1\)

考虑没有操作 \(3\),答案是 \((0,1)\) 走到 \((A,B)\) 不触碰 \(y=x\) 的方案数。考虑沿直线翻着得到从 \((1,0)\) 走到 \((B,A)\) 的方案数,容斥可得:

\[res=\binom{A+B-1}{A-1}-\binom{A+B-1}{A} \]

考虑加上操作 \(3\),由于移动点不好直接计数,考虑把 \(y= x\) 直线下移,设最后一次下移量为 \(k\),即我们使用操作三最后一次使 \(b\) 上移了 \(k\) 个单位长度。那么我们的终点变成 \((A,B-k)\),现在只需要考虑把 \(n-A-(B-k)-1\) 的操作次数插入路径即可,减一是因为钦定最后一步移动了 \(k\) 格。

所以就是插板法把 \(n-A-(B-k)-1\) 分配给 \(k+1\) 个格子。

\(k+1\) 个格子是因为我们只能在 \(y=x-t,t\in[0,k]\) 与折线的最后一个交点提升。

为什么是最后一个交点?因为我们钦定操作二不能触碰 \(y=x\),改成相对运动后就是不能触碰 \(y=x-t\),所以对于最后一个交点以后的点,无法再到达 \(y=x-t\)

所以答案为:

\[F(n,m)=\binom{n+m-1}{n-1}-\binom{n+m-1}{n} \]

\[ans=\sum\limits_{k=1}^B F(A,B-k)\binom{n-A-(B-k)-1+k}{k} \]

特别的,当 \(A+B=n\) 时,还有不使用操作 \(3\) 的贡献。

总结

  • orz p_b_p_b
  • 重要观察 \(1\):转化折线图,并把提升坐标转化下移直线
  • 重要观察 \(2\):只有最后一次碰到 \(y=x-t\) 时才能使用操作 \(3\),并且位置数 有 \(k+1\) 个。
  • Code

#include<bits/stdc++.h>
#define ll long long
#define N 10000005
#define endl "\n" 
#define fi first
#define se second
using namespace std;
const ll mod=998244353;
const ll inf=1e18;
const double eps=1e-6;

namespace math_permutation{
    ll fpow(ll x,ll y){
        ll res=1;
        while(y){
            if(y&1)res=res*x%mod;
            y>>=1,x=x*x%mod;
        }
        return res;
    }
    ll fac[N],ifac[N],inv[N];
    void work(ll r){
        fac[0]=inv[1]=1;
        for(int i=1;i<=r;i++)fac[i]=fac[i-1]*i%mod;
        ifac[r]=fpow(fac[r],mod-2);
        for(int i=r;i>0;i--){
            ifac[i-1]=ifac[i]*i%mod;
            inv[i]=fac[i-1]*ifac[i]%mod;
        }
    }
    ll C(ll n,ll m){
       if(n<0||n<m)return 0;
        return fac[n]*ifac[m]%mod*ifac[n-m]%mod;
    }
    ll catlan(ll n,ll m){
        return (C(n+m-1,n-1)-C(n+m-1,n)+mod)%mod;
    }
}using namespace math_permutation;
ll n,a,b;
int main(){
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    work(N-5);
    cin>>n>>b>>a;
    if(a+b==0){
        cout<<1<<endl;
        return 0;
    }
    ll res=0;
    for(int i=0;i<=b;i++)res=(res+catlan(a,b-i)*(C(n-a-(b-i)-1+i,i))%mod)%mod;
    if(a+b==n)res=(res+catlan(a,b))%mod;
    cout<<res<<endl;
    return 0;
}
posted @ 2024-09-27 14:34  yshpdyt  阅读(40)  评论(0)    收藏  举报