AtCoder Regular Contest 169 (ARC169)

怎么有人 ARC A 卡了半天的?

A. Please Sign

考虑 \(i\) 位置上的数,下次它被加到 \(P_i\),再下次被加到 \(P_{P_i}\),因为这个序列有性质 \(P_i<i\),这样加若干轮一定会到达 \(1\)
令所有的 \(i\)\(P_i\) 连边,则这是一棵以 \(1\) 为根的树。

\(f_i=\sum\limits_{j=1}^n [dep_j=i] a_j\)。其中 \(dep_1=0\)
那么 \(A_1=f_0\),一次操作相当于令所有 \(f_i\gets f_i+f_{i+1}\)

首先如果 \(f\) 数组全都是 \(0\)\(A_1\) 操作很多次后显然还是 \(0\)
考虑 \(f\) 最后一个不为 \(0\) 的位置的符号,设这个数为 \(f_x\)。若 \(f_x>0\),由于每次令 \(f_{x-1}\gets f_{x-1}+f_x\)\(f_{x-1}\) 一定会在若干次操作后变为正数。这可以类似地推广到 \(f_{x-2},f_{x-3},\dots\)。因此操作次数足够多时,有 \(f_0>0\),即 \(A_1>0\)

\(f_x<0\) 同理。综上,我们得到结论:\(A_1\) 最后的符号与 \(f\) 数组最后一个不为 \(0\) 的位置符号相同。

Code
#define int long long
const int N=2.5e5+5,inf=2e9;
int n,a[N],p[N],f[N],dep[N],mx;
vector<int> e[N];
void dfs(int u)
{
    mx=max(mx,dep[u]);
    for(auto v:e[u]) 
    {
        dep[v]=dep[u]+1;
        dfs(v);
    }
}
signed main()
{
    n=read();
    for(int i=1;i<=n;i++) a[i]=read();
    for(int i=2;i<=n;i++) p[i]=read(),e[p[i]].push_back(i);
    dfs(1);
    for(int i=2;i<=n;i++) f[dep[i]]+=a[i];
    int flag=0;
    for(int i=n;i;i--)
        if(f[i]) {flag=f[i]>0?1:-1;break;}
    a[1]=flag*inf+a[1];
    if(a[1]>0) cout<<"+";
    else if(a[1]==0) cout<<"0";
    else cout<<"-";
    return 0;
}

B. Subsegments with Small Sums

先考虑一个确定的区间怎么求答案。

直接贪心,从左往右考虑这个序列,如果上一段能放下就放进上一段,否则新开一段。正确性比较显然。
那这个题就能做了:考虑左端点相同时每个右端点的答案,设这个和为 \(f_l\)。分为两种情况:

  • 只有一段,对答案的贡献为 \(1\)
  • 有至少两段,但根据上面的贪心,它们第一段结束的位置均相同。设这个位置是 \(x\)

那么 \(r\ge x\) 的区间可以看作 \([x,r]\) 的答案加 \(1\),即有转移 \(f_l\gets f_x+(n-l+1)\),答案为 \(ans=\sum\limits_{i=1}^n f_i\)
\(x\) 的过程双指针或二分均可。

Code
#define int long long
const int N=2.5e5+5;
int n,s,sum[N],a[N];
int f[N];
signed main()
{
    n=read(),s=read();
    for(int i=1;i<=n;i++) a[i]=read(),sum[i]=sum[i-1]+a[i];
    for(int i=n;i;i--)
    {
        int nxt=lower_bound(sum+1,sum+n+1,sum[i-1]+s+1)-sum;
        f[i]=f[nxt]+(n-i+1);
    }
    int ans=0;
    for(int i=1;i<=n;i++) ans+=f[i];
    printf("%lld\n",ans);
    return 0;
}

C. Not So Consecutive

读错题了,写了一车组合数还在想为什么过不去最后一个样例。

\(f_{i,j}\) 表示填了前 \(i\) 个位置,第 \(i\) 个位置填的是 \(j\) 的方案数。那么枚举这个连续段的起点,朴素转移有

\[f_{i,j}=\sum_{k=\max(0,i-j)}^{i-1} [\forall l\in [k+1,i],A_l=-1 \text{ or } A_l=j]\sum_{col\neq j} f_{k,col} \]

这样直接做是 \(\mathcal{O}(n^4)\) 的,考虑优化。

首先方括号里那一串式子本质上是找 \(i\) 前面最后一个填了不为 \(j\) 的数的位置,设这个位置为 \(lst\)

对每个数维护 \(pos_j\) 表示数 \(j\) 当前最后一次出现的位置。那么对于每个新的 \(i\),我们记录 \(pos\) 的最大值和次大值即可。

这个过程对每个 \(i\) 可以 \(\mathcal{O}(n)\) 完成。式子变成了

\[f_{i,j}=\sum_{k=lst}^{i-1} \sum_{col\neq j} f_{k,col} \]

再把 \(col\neq j\) 这个条件容斥掉,有

\[f_{i,j}=\sum_{k=lst}^{i-1} \sum_{col=1}^n f_{k,col}-\sum_{k=lst}^{i-1}f_{k,j} \]

\(S_i=\sum\limits_{j=1}^i \sum\limits_{col=1}^n f_{i,col}\)\(s_{i,j}=\sum\limits_{k=1}^i f_{k,j}\),前缀和优化即可。时间复杂度 \(\mathcal{O}(n^2)\)

Code
#define int long long
const int N=5005,mod=998244353;
int pos[N],f[N][N],s[N][N],sum[N],n,a[N];
signed main()
{
    n=read();
    for(int i=1;i<=n;i++) a[i]=read();
    f[0][0]=1,sum[0]=1;
    for(int i=1;i<=n;i++)
    {
        if(a[i]!=-1) pos[a[i]]=i;
        int mx1=0,mx2=0;
        for(int j=1;j<=n;j++) 
        {
            if(pos[j]>mx1) mx2=mx1,mx1=pos[j];
            else if(pos[j]>mx2) mx2=pos[j];
        }
        for(int j=1;j<=n;j++)
        {
            int lst=max(i-j,(a[mx1]==j)?mx2:mx1);
            f[i][j]=sum[i-1]-(lst?sum[lst-1]:0)-(s[i-1][j]-(lst?s[lst-1][j]:0));
            f[i][j]=(f[i][j]%mod+mod)%mod;
            s[i][j]=(s[i-1][j]+f[i][j])%mod;
            (sum[i]+=f[i][j])%=mod;
        }
        (sum[i]+=sum[i-1])%=mod;
    }
    cout<<(sum[n]-sum[n-1]+mod)%mod<<endl;
    return 0;
}

D. Add to Make a Permutation

设序列 \(A\) 操作得到未被取模的最终序列 \(B\),那么 \(B\) 应当满足如下条件:

  • \(B_i\ge A_i\)
  • \(B_i\bmod N\) 两两不同;
  • \(S=\sum\limits_{i=1}^n (B_i-A_i)\),有 \(S\bmod M=0\)
  • \(\max\{B_i-A_i\}\le \frac{S}{M}\)

考虑第三个条件,在一定的操作次数下 \(\max\{B_i-A_i\}\) 越小越容易满足条件。将 \(A\) 序列升序排序,最优的对应关系一定是将 \(B\) 也升序排序。

发现答案只和 \(S\) 的值有关,有结论:若有解,一定存在一种最优方案形如 \(B=(x,x+1,\cdots ,x+N)\)

使用调整法证明。假设我们有一个最优的 \(B\) 序列,且 \(B_N-B_1>N\)。那么令 \(B_1\gets B_N-N,B_N\gets B_1+N\),依次对照上文的所有条件:

  • 首先因为 \(B_N-N>B_1\),新的 \(B_1\) 比原来大。同时因为 \(B_1>A_1,A_N-A_1<N\),有 \(B_1+N>A_N\),满足条件 1。
  • 加减 \(N\) 不改变取模后的值。
  • 总和没变,\(S\) 不变。
  • \(B_N-A_N\) 一定变小。前者有 \(B_N-A_N>B_N-N-A_1\),所以也变小,满足条件。

综上,这样调整后得到的新序列仍合法。

那么我们只需考虑最小化 \(x\) 的值。

根据第一个条件可以得到 \(x\) 的下界。第四个条件 \(x\) 每增加 \(1\),不等式左侧增加 \(1\),右侧增加 \(\frac{N}{M}\)。因为 \(N>M\),第四个条件也为 \(x\) 提供了下界。

接下来令 \(x\) 满足第三个条件即可,发现 \(x\)\(x+N\) 是等价的,\(x\) 可以直接枚举 \(N\) 次。

时间复杂度是 \(\mathcal{O}(n)\)

Code
#define int long long
const int N=2.5e5+5;
int n,m,a[N],sum,x;
signed main()
{
	n=read(),m=read();
	for(int i=1;i<=n;i++) a[i]=read();
	sort(a+1,a+n+1);
	for(int i=1;i<=n;i++) x=max(x,a[i]-i+1);
	for(int i=1;i<=n;i++) sum+=(x+i-1)-a[i];
	bool flag=0;
	for(int i=0;i<=n;i++) if((sum+n*i)%m==0) {x+=i,sum+=n*i,flag=1;break;}
	if(!flag) {printf("-1\n");return 0;}
	int mx=0;
	for(int i=1;i<=n;i++) mx=max(mx,x+i-1-a[i]);
	while(mx>sum/m) mx+=m/__gcd(n,m),sum+=n*m/__gcd(n,m);
	printf("%lld\n",sum/m);
	return 0;
}

E. Avoid Boring Matches

\(\texttt{R}\)\(\texttt{B}\) 多一定无解,否则可以换成 \(\texttt{BBBB...RRR}\) 的形式,一定有解。

先找判断序列合法的充要条件。

考虑每轮的最优匹配策略是什么样的,我们希望更多的 \(\texttt{B}\) 能留到下一轮,因此要尽可能地让 \(\texttt{B}\) 和它后面的 \(\texttt{R}\) 配对。在多种配对方案能保留的 \(\texttt{B}\) 数量相同时,我们希望留下的 \(\texttt{B}\) 位置尽可能靠前,因为这样在下下轮它们被保留下来的概率更大。

因此我们得到这样的贪心策略:从左到右考虑每个 \(\texttt{B}\),将它和右边第一个未被配对的 \(\texttt{R}\) 配对。最终将所有剩下未配对的数两两配对。根据上文可以知道这样做是最优的。

\(t_i\) 表示长度为 \(2^i\),且所有 \(\texttt{B}\) 尽可能靠右的合法解。

\(t_0=\texttt{R}\)
考虑怎么从 \(t_{i-1}\) 得到 \(t_i\)。从左到右处理 \(t_{i-1}\) 的每一位,若当前位是 \(\texttt{R}\),表示这位没被匹配,\(t_i=t_i+\texttt{R}\)(\(+\) 表示字符串拼接);当前位是 \(\texttt{B}\),我们希望下一个 \(\texttt{B}\) 尽量靠后,即与之匹配的 \(\texttt{R}\) 尽量更近,\(t_i=t_i+\texttt{BR}\)。不足 \(2^i\) 位用 \(\texttt{B}\) 补齐。

\(t_n\) 的第 \(j\)\(\texttt{B}\) 的位置为 \(T_j\),原序列位置为 \(S_j\)。那么若存在 \(S_j>T_j\),则该序列不合法。证明大概是如果一个 \(S_j<T_j\),匹配数不会变多;但如果 \(S_j>T_j\)\(\texttt{BR}\) 的匹配数一定减少。

答案是令 \(S\) 满足上述条件的最小操作次数,即 \(\sum\limits_{j=1}^{2^n} \max(0,T_j-S_j)\)。时间复杂度 \(\mathcal{O}(2^n)\)

Code
const int N=(1<<18)+5;
int n;
string s,t[N];
int tot,pos[N];
int main()
{
	n=read(); cin>>s;
	t[0]="R";
	for(int i=1;i<=n;i++)
	{
		for(auto c:t[i-1]) 
			if(c=='R') t[i]+="R";
			else t[i]+="BR";
		while(t[i].size()<(1<<i)) t[i]+="B";
	}
	for(int i=0;i<(1<<n);i++) if(t[n][i]=='B') pos[++tot]=i;
	tot=0; long long ans=0;
	for(int i=0;i<(1<<n);i++) if(s[i]=='B') 
	{
		tot++;
		if(i>pos[tot]) ans+=i-pos[tot];
		if(tot==(1<<n-1)) break;
	}
	if(tot<(1<<n-1)) printf("-1\n");
	else printf("%lld\n",ans);
	return 0;
}

F. Large DP Table

貌似不会。

  • 19
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值