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 ...
随机推荐
- 【c++ Prime 学习笔记】目录索引
第1章 开始 第Ⅰ部分 C++基础 第2章 变量和基本类型 第3章 字符串.向量和数组 第4章 表达式 第5章 语句 第6章 函数 第7章 类 第 Ⅱ 部分 C++标准库 第8章 IO库 第9章 顺序 ...
- 配置pyenv环境
git clone https://github.com/pyenv/pyenv.git ~/.pyenv echo 'export PYENV_ROOT="$HOME/.pyenv&quo ...
- win10安装git fatal: open /dev/null or dup failed: No such file or directory错误解决方法
原因看大家意思应该是 非即插即用驱动文件null.sys问题. 网上有很多方案.最后试了一个可行的. 替换 windows/system32/drivers/null.sys为网盘中的文件即可. 链 ...
- Beta阶段第五次会议
Beta阶段第五次会议 时间:2020.5.21 完成工作 姓名 工作 难度 完成度 ltx 1.对小程序进行修改和美化新增页面(新增60行) 中 85% xyq 1.编写技术博客 中 85% xtl ...
- pyqgis环境配置
配置pyqgis开发环境时,很多网上教程写的非常繁琐,这里仅仅找了一个最简单的配置方法,使用pycharm的IDE,安装QGIS软件后,在pycharm的ProjectInterpreter里面填写Q ...
- 嵌入式大佬给你分析stm32串口
stm32作为现在嵌入式物联网单片机行业中经常要用多的技术,相信大家都有所接触,今天这篇就给大家详细的分析下有关于stm32的出口,还不是很清楚的朋友要注意看看了哦,在最后还会为大家分享有些关于stm ...
- 黑客是如何利用DNS域传送漏洞进行渗透与攻击的?
一.DNS域传送 DNS :Domain Name System 一个保存IP地址和域名相互映射关系的分布式数据库,重要的互联网基础设施,默认使用的TCP/UDP端口号是53 常见DNS记录类型: 1 ...
- Java测试开发--MySql之C3P0连接池(八)
连接池C3P0! 连接池技术的目的:解决建立数据库连接耗费资源和时间很多的问题,提高性能 ! 下面以案例演示下C3P0的操作流程. 1.测试准备: ①MySql数据库一枚②database名为myte ...
- javascript 深拷贝与浅拷贝
javascript 深拷贝与浅拷贝 深拷贝与浅拷贝 赋值和深/浅拷贝的区别 浅拷贝的实现方式 1.Object.assign() 2.函数库lodash的_.clone方法 3.展开运算符... 4 ...
- linux 入门系列-基础性知识
1:初探linux-基于centos7 运维和服务器硬件组合 两种登录方式:(1)-------root:管理员登录权限较高,不建议初学者使用格式: [root@centos7 jinlong]# ( ...