Atcoder Grand Contest 054 题解
那天晚上由于毕业晚会与同学吃饭喝酒没打 AGC,第二天稍微补了下题,目前补到了 E,显然 AGC 的 F 对于我来说都是不可做题就没补了(bushi
A
简单题,不难发现如果我们通过三次及以上的操作将这个串消完,那么我们完全可以把它压缩到两次以内,因此如果两段字符不同答案就是 \(1\),否则我们枚举分割点然后判断分割点两段是否都可以一次消完,如果存在这样的分割点答案就是 \(2\),否则答案为 \(-1\)。
B
注意到如果我们将原序列分成和相等的两部分并两部分将它们排成一列,那么有且只有一种排列方式对应出来是这个序列,具体证明就哪边小就往哪边塞即可,这个很好理解,因此我们可以设 \(dp_{i,j,k}\) 表示在前 \(i\) 个数里面选择了 \(k\) 个数,和为 \(j\) 的方案数,转移就正常按照背包的套路转移即可,记 \(S=\sum\limits_{i=1}^nw_i\),那么最终答案即为 \(\sum\limits_{i=1}^{n-1}dp_{n,S/2,i}·i!·(n-i)!\)。时间复杂度 \(\mathcal O(n^4)\)
C
带家好,这就是一个猜出结论然后没敢写的 sb,晚上交了一发,竟然过了
知乎习惯:先抛结论,再给证明。记 \(S\) 为满足逆序对数为 \(k\) 的下标的集合,那么答案即为 \(\prod\limits_{x\in S}(n-x+1)\)
证明:这是我自己 yy 出来的一个证明,错了不要打我
首先考虑对于一个固定的排列 \(p\),最优策略长什么样,显然每次交换最多使得某个 \(p_i\) 的逆序对数减 \(1\),我们记 \(p_i\) 的逆序对数为 \(x_i\),故交换次数有下界 \(\sum\limits_{i=1}^n\max(x_i-k,0)\),而如果我们每次都找到一个满足 \(x_i>k\) 且 \(p_{i-1}>p_i\) 的下标 \(i\) 并交换 \(p_{i-1},p_i\),那么每次交换都能使 \(x_i>k\) 的逆序对数恰好 \(-1\),也就能达到这个下界。那么问题又来了,是否每次都能找到这样的下标 \(i\) 呢?答案是肯定的,考虑反证法,如果对于所以 \(x_i>k\) 的 \(i\) 都有 \(p_{i-1}<p_i\),那么由于 \(p_{i-1}<p_i\),必然有 \(x_{i-1}\ge x_i>k\),同理可得 \(p_{i-2}<p_i,x_{i-2}\ge x_{i-1}\ge x_i>k\),如此一直推下去可以推得 \(p_1<p_2<p_3<\cdots<p_i\),即 \(x_i=0\),而 \(k\ge 0\),矛盾!因此我们有了以下算法:
Algorithm. 每次找到一个满足 \(x_i>k,p_{i-1}>p_i\) 的下标 \(i\) 并交换 \(p_{i-1},p_i\),如此进行下去直到不存在 \(x_i>k\) 即可。
接下来考虑原问题,假设满足 \(x_i=k\) 的下标集合为 \(S=\{i_1,i_2,\cdots,i_k\}(i_m<i_{m+1},m\in[1,k-1])\),那么我们只需证明对于任意将 \(S\) 中的元素相对位置向右移的方案都有它还原出来的排列为 \(P'\)。
由于对于最终排列 \(P'\) 而言都有 \(x_i\le k\),因此 \(p'_{i_t}\) 必然小于 \(p'_{i_t+1},p'_{i_t+2},\cdots,p'_n\),否则假设 \(p'_j<p'_{i_t},j>i_t\),那么 \(j\) 的逆序对数大于 \(x_{i_t}=k\),矛盾。又 \(i_m<i_{m+1},m\in[1,k-1]\),故我们有 \(p'_{i_m}<p'_{i_{m+1}}\),我们考虑原排列中 \(p'_{i_1}\) 的位置 \(v\),显然根据上面的推论如果 \(v\ne i_1\) 必然有 \(x_v>k\) 且 \(\forall j\in[i_1,v-1],p_j>p_{i_1}\),因此我们会一直向左移动 \(i_1\) 直到它的逆序对数 \(=k\) 为止,而显然这个终止位置就是最后的 \(i_1\),如此继续下去,还原 \(i_2,i_3,\cdots,i_m\) 即可得到排列 \(P'\)(当然我这里证明比较玄乎,如果严谨证明的话可用归纳法表述)
时间复杂度 \(\mathcal O(n\log n)\)
最慢的一个点跑了 7ms,也太慢了吧(说反话 ing
D
神仙题。
首先看到最少交换次数可以自然想到 \(dp\),\(dp_{i,j,k,l}\) 表示填了 \(i\) 个 (
,\(j\) 个 )
,\(k\) 个 o
,\(l\) 个 x
的最少交换次数,根据这题的套路,最小的交换次数就是原序列与交换后的序列中相对位置发生改变的字符对数,也就是说加入一个字符后对交换次数的贡献是可以 \(\mathcal O(1)\) 求出来的,然鹅并没有什么卵用,这个状态就已经达到了 \(n^4\),一脸过不去。
于是我们不妨来分析一下这个东西有什么性质,看到括号序列我们可以很自然地将左括号当作 \(1\),右括号当作 \(-1\),那么我们可以将这四个字符分为两类,()
(表示的数为 \(0\))和 ox
(对应的数为 \(0\)),那么可以发现以下性质:
Observation 1. 我们显然不会交换
ox
证明:如果我们交换的 ox
这类字符中有 o
,由于 o
展开来是 ()
,本来就是一个合法的字符串,因此 o
的位置并不影响括号串的合法性,而如果我们交换的两个字符都是 x
那我们交换它们干嘛呢。
这样一来我们假设括号序列的顺序为 \(S\),那么括号序列内部的交换次数显然是可以扫一遍求出的,而 ()
与 ox
两类字符对答案的贡献是可以 \(dp\) 的,\(dp_{i,j}\) 表示填好了 \(S\) 的前 \(i\) 位和 ox
的序列的前 \(j\) 位的最少交换次数,这个 \(dp\) 显然是可以在常数时间内转移的,注意如果括号序列前缀和为 \(0\) 就不能放 x
。因此这部分时间复杂度是 \(\mathcal O(\dfrac{n^2}{4})\) 的,没有问题,也就是说如果我们能确定 \(S\) 的顺序那这题就做完了。
那怎么确定 \(S\) 的顺序呢?
Observation 2. 最终的括号序列一定是在原括号顺序的前提下,使用最少交换次数交换求得的括号序列
说人话,就是如果原来左括号右括号顺序是 \(S'\),那么设我们通过最少次交换使 \(S'\) 变成合法括号序列后,\(S'\) 变成了 \(S\),那么 \(S\) 就是最终括号串的顺序。
为什么?这里再给出一个更不严谨的证明。不难发现我们交换括号序列的原因只有一点,就是使得不能放 x
的地方可以放 x
,但事实上我们与其交换这些括号,还不如交换这些 x
们,把它们放到括号中来得更优,比方说 ()xxxx()
,如果我们交换括号的话要将最后一个 x
后面的 (
移动五格,而如果我们移动 x
只要将每个 x
向右移动 \(1\) 格,总共四步,因此我们大可不必花这些精力移动括号。(真是玄乎到不能再玄乎了呢,哪位好心的鸽鸽知道这东西的 proof 麻烦来教教蒟蒻呗/kel)
于是这题就真的做完了(
const int MAXN=8e3;
int n,n1,n2,ans=0,ps1[MAXN+5],ps2[MAXN+5];
char s[MAXN+5],s1[MAXN+5],s2[MAXN+5];
int dp[MAXN+5][MAXN+5],pre1[MAXN+5],pre2[MAXN+5],preo[MAXN+5],prex[MAXN+5];
int sum1[MAXN+5],sum2[MAXN+5],sumo[MAXN+5],sumx[MAXN+5];
int main(){
scanf("%s",s+1);n=strlen(s+1);
for(int i=1;i<=n;i++) (s[i]>97)?(s2[++n2]=s[i],ps2[n2]=i):(s1[++n1]=s[i]);
for(int i=1;i<=n;i++){
pre1[i]=pre1[i-1]+(s[i]=='(');
pre2[i]=pre2[i-1]+(s[i]==')');
preo[i]=preo[i-1]+(s[i]=='o');
prex[i]=prex[i-1]+(s[i]=='x');
} //cout<<s1+1<<endl;
for(int i=1,s=0;i<=n1;i++){
s+=1-((s1[i]==')')<<1);
if(s<0){
s=1;
for(int j=i+1;j<=n1;j++) if(s1[j]=='('){
// printf("%d %d\n",j,i);
swap(s1[j],s1[i]);ans+=j-i;break;
}
}
} memset(dp,63,sizeof(dp));dp[0][0]=0;
// cout<<s1+1<<endl;printf("%d\n",ans);
for(int i=1,p1=0,p2=0;i<=n1;i++){
if(s1[i]=='('){
do{p1++;} while(s[p1]!='(');
ps1[i]=p1;
} else {
do{p2++;} while(s[p2]!=')');
ps1[i]=p2;
}
}
for(int i=1;i<=n1;i++){
sum1[i]=sum1[i-1]+(s1[i]=='(');
sum2[i]=sum2[i-1]+(s1[i]==')');
}
for(int i=1;i<=n2;i++){
sumo[i]=sumo[i-1]+(s2[i]=='o');
sumx[i]=sumx[i-1]+(s2[i]=='x');
}
// for(int i=1;i<=n1;i++) printf("%d%c",ps1[i]," \n"[i==n1]);
// for(int i=1;i<=n2;i++) printf("%d%c",ps2[i]," \n"[i==n2]);
// for(int i=1;i<=n;i++) printf("%d%c",pre1[i]," \n"[i==n]);
// for(int i=1;i<=n;i++) printf("%d%c",pre2[i]," \n"[i==n]);
// for(int i=1;i<=n;i++) printf("%d%c",prex[i]," \n"[i==n]);
// for(int i=1;i<=n;i++) printf("%d%c",preo[i]," \n"[i==n]);
for(int i=0,s=0;i<=n1;i++){
for(int j=0;j<=n2;j++){
if(i^n1) chkmin(dp[i+1][j],dp[i][j]+max(sumo[j]-preo[ps1[i+1]],0)+max(sumx[j]-prex[ps1[i+1]],0));
if(j^n2&&(s||(s2[j+1]=='o'))) chkmin(dp[i][j+1],dp[i][j]+max(sum1[i]-pre1[ps2[j+1]],0)+max(sum2[i]-pre2[ps2[j+1]],0));
} s+=1-((s1[i+1]==')')<<1);
} printf("%d\n",ans+dp[n1][n2]);
// for(int i=0;i<=n1;i++) for(int j=0;j<=n2;j++) printf("%d %d %d\n",i,j,dp[i][j]);
return 0;
}
E
知乎好习惯:只抛结论,不给证明,因为我不会证明,Atcoder 题解也写得不是太详细
Observation. 以 \(p_1<p_n\) 的情况为例,序列合法当且仅当 \(\exists k\in[1,n-1]\) 满足 \(p_k\le p_1,p_{k+1}\ge p_n\)
证明不会
考虑怎样计算这个东西,咱们不妨从反面入手,拿总方案减去不合法的方案数,记 \(f(n,A)\) 表示 \(p_1=A,p_n>p_1\) 的不合法的序列的个数,那么 \(ans=(n-1)!-f(n,A)-f(n,n-A)\),因此我们只需求出 \(f(n,A)\) 即可。
考虑怎样求 \(f(n,A)\),我们假设 \(p_n=p_1+k\),那么 \((p_1,p_n)\) 这 \(k-1\) 个数随便填,\((k-1)!\) 种可能,当我们依次填入 \([1,p_1)\) 时,填入 \(i\) 时有 \(k-2+i\) 种填法,再依次填入 \((p_n+1,n]\) 时,填入 \(p_n+i\) 时有 \(k-2+i\) 种填法,乘在一起是 \((k-1)!\times\dfrac{(k-2+A-1)!}{(k-2)!}\times\dfrac{(k-2+n-A-k)!}{(k-2)!}=(A+k-3)!\times(n-A-2)!\times\dfrac{1}{(k-2)!}\times(k-1)\),因此填法总数就是 \(\sum\limits_{2\le k\le n-A}(A+k-3)!\times(n-A-2)!\times\dfrac{1}{(k-2)!}\times(k-1)=(n-A-2)!\sum\limits_{2\le k\le n-A}\dbinom{A+k-3}{k-2}(k-1)\),再推个柿子可以得到:
ans&=(n-A-2)!\sum\limits_{0\le k\le n-A-2}\dbinom{A+k-1}{k}(k+1)\\
&=(n-A-2)!(\sum\limits_{0\le k\le n-A-2}\dbinom{A+k-1}{k}k+\sum\limits_{0\le k\le n-A-2}\dbinom{A+k-1}{k})\\
&=(n-A-2)!(\sum\limits_{0\le k\le n-A-2}\dbinom{A+k-1}{k-1}A+\sum\limits_{0\le k\le n-A-2}\dbinom{A+k-1}{k}))\\
&=(n-A-2)!(A·\sum\limits_{0\le k\le n-A-3}\dbinom{A+k}{A}+\sum\limits_{0\le k\le n-A-2}\dbinom{A+k-1}{A-1})\\
&=(n-A-2)!(A·\dbinom{n-2}{A+1}+\dbinom{n-2}{A})
\end{aligned}
\]
大功告成。
搬 Atcoder 题解 ing
const int MAXN=2e6;
const int MOD=998244353;
int fac[MAXN+5],ifac[MAXN+5];
void init_fac(int n){
for(int i=(fac[0]=ifac[0]=ifac[1]=1)+1;i<=n;i++) ifac[i]=1ll*ifac[MOD%i]*(MOD-MOD/i)%MOD;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i-1]*ifac[i]%MOD;
}
int binom(int x,int y){
if(x<0||y<0||x-y<0) return 0;
return 1ll*fac[x]*ifac[y]%MOD*ifac[x-y]%MOD;
}
int calc(int n,int A){
return 1ll*fac[n-A-2]*fac[A-1]%MOD*((1ll*A*binom(n-2,A+1)+binom(n-2,A))%MOD)%MOD;
}
void solve(){
int n,A;scanf("%d%d",&n,&A);
printf("%d\n",(0ll+fac[n-1]-calc(n,A)-calc(n,n-A+1)+MOD+MOD)%MOD);
}
int main(){
init_fac(MAXN);
int qu;scanf("%d",&qu);while(qu--) solve();
return 0;
}
Atcoder Grand Contest 054 题解的更多相关文章
- AtCoder Grand Contest 017 题解
A - Biscuits 题目: 给出 \(n\) 个物品,每个物品有一个权值. 问有多少种选取方式使得物品权值之和 \(\bmod\space 2\) 为 \(p\). \(n \leq 50\) ...
- AtCoder Grand Contest 030题解
第一次套刷AtCoder 体验良好 传送门 Poisonous Cookies cout<<b+min(c,a+b+); Tree Burning 难度跨度有点大啊 可以证明当第一次转向之 ...
- AtCoder Grand Contest 031题解
题面 传送门 题解 比赛的之后做完\(AB\)就开始发呆了--简直菜的一笔啊-- \(A - Colorful\ Subsequence\) 如果第\(i\)个字母选,那么它前面任意一个别的字母的选择 ...
- AtCoder Grand Contest 039 题解
传送门 \(A\) 首先只有一串的情况下,遇到相同的肯定是改后面那一个最优,然后两串的话可能要分奇偶讨论一下 //quming #include<bits/stdc++.h> #defin ...
- AtCoder Grand Contest 017题解
传送门 \(A\) 直接转移就是了 typedef long long ll; const int N=55; ll f[N][2];int a[N],n,p; int main(){ scanf(& ...
- AtCoder Grand Contest 015题解
传送门 \(A\) 找到能达到的最大的和最小的,那么中间任意一个都可以被表示出来 typedef long long ll; int n,a,b;ll res; int main(){ scanf(& ...
- AtCoder Grand Contest 038 题解
传送门 这场表现的宛如一个\(zz\) \(A\) 先直接把前\(b\)行全写成\(1\),再把前\(a\)列取反就行 const int N=1005; char mp[N][N];int n,m, ...
- AtCoder Grand Contest 014题解
传送门 \(A\) 首先大力猜测一下答案不会很大,所以次数大于\(10^6\)输出\(-1\)就行了 不过我并不会证上界,据说是因为如果\(a=b=c\)且都是偶数肯定\(-1\),否则设\(a\le ...
- AtCoder Grand Contest 011题解
传送门 \(A\) 直接按时间排序之后贪心就可以了 const int N=1e5+5; int a[N],q[N],c,k,h,t,n,res; inline int min(R int x,R i ...
随机推荐
- 【UE4 C++】 UnrealPak 与 Pak 的制作、挂载、加载
简介 通过 UnrealPak,可以将资源打包成 Pak 文件 Pak文件是UE4游戏生成的数据包文件. Pak 之前一般先有 Cooked 步骤,将资源烘焙为对应平台支持的资源 一般打包后的项目使用 ...
- 短短 29 天,应对高峰 100W+ 访问,看浙大如何交出满分答卷
疫情期间"停课不停教,停课不停学",线上开课第一天,浙江大学网上开课平台访问量即突破100 万次,访客数3万余人,最高峰达 1.1万人同时在线,发起课程直播2000余场,然而系统却 ...
- kafka错误之 Topic xxx not present in metadata after 60000 ms
Topic xxx not present in metadata after 60000 ms 一.背景 二.场景还原 1.jar包引入 2.jar代码 3.运行结果 三.问题解决 四.参考文档 一 ...
- 用STM32内置的高速ADC实现简易示波器
做一个数字采样示波器一直是我长久以来的愿望,不过毕竟这个目标难度比较大,涉及的方面实在太多,模拟前端电路.高速ADC.单片机.CPLD/FPGA.通讯.上位机程序.数据处理等等,不是一下子就能成的,慢 ...
- Python课程笔记(六)
今天上课补上了上次未学完比较重点的鼠标和键盘事件,同时开始学习运用turtle进行绘图. 本次课程的代码: https://gitee.com/wang_ming_er/python_course_l ...
- Spring的Ioc与DI
一.前言 Spring框架的核心基于控制反转的原理. IoC是一种将组件依赖关系的创建和管理外部化的技术. 考虑一个示例,其中Foo类依赖于Bar类的实例来执行某种处理. 传统上,Foo使用new运算 ...
- Verilog设计技巧实例及实现
Verilog设计技巧实例及实现 1 引言 最近在刷HDLBits的过程中学习了一些Verilog的设计技巧,在这里予以整理.部分操作可能降低代码的可读性和Debug的难度,请大家根据实际情况进行使用 ...
- PyPi到底是什么?pypi有啥作用?PyPi和pip有何渊源?
转载:https://blog.csdn.net/weixin_42139375/article/details/82711201 可能有很多刚入行不久的朋友们,每天都在用pip 命令install ...
- linux hostid与lmhostid
https://wangchujiang.com/linux-command/c/hostid.html hostid(host identifier) 显示当前主机的十六进制数字标识. 概要 hos ...
- Luogu 520题纪念
一入OI深似海......