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}\) 的位置可以任意填 B,C。
考虑对于一串 AAAAA 有两种对应方式: ABABA 或 BABAB,于是不能删部分的答案要乘二。
答案为:
总结
- 重要观察:把
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\) 这样便分离的贡献,转移方程较为简单:
转移是 \(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)\)。
更改三个操作:
- \(a\) 走:\(x=x+1\)
- \(b\) 走:\(y=y+1\)
- \(b\) 移到 \(a\):\(y=x\)
注意到 \(y=x-1\) 时操作 \(2,3\) 等效,考虑操作 \(2\) 不能越过 \(y=x-1\)。
考虑没有操作 \(3\),答案是 \((0,1)\) 走到 \((A,B)\) 不触碰 \(y=x\) 的方案数。考虑沿直线翻着得到从 \((1,0)\) 走到 \((B,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\)。
所以答案为:
特别的,当 \(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;
}

浙公网安备 33010602011771号