大概就 lxr 讲了 4 个 AGC 的 D,有一个以前做过了不算,另外三个都会做罢(

为了避免开三个博客就把它们合并到一起了

AGC 037 D

lxr:难度顺序排列大概是 037<043<050

没错我也是这么认为的,如果按照我思考的时间来看,大概是 037 10min,043 50min,050 1h

首先注意到此题操作的特殊性:行能够交换两次,而列只能被交换一次,因此对于每个 \(a_{i,j}\),它必须在第二次(也就是 \(B\to C\) 这一步)从第 \(i\) 行交换到第 \(\lfloor\dfrac{a_{i,j}-1}{m}\rfloor\) 行,因此我们考虑对于这样的 \(a_{i,j}\) 连一条 \(i\to\lfloor\dfrac{a_{i,j}-1}{m}\rfloor\) 的边,那么对于 \(B\) 矩阵同一列上的元素对应的边,它们必须满足起点互不相同,终点也互不相同。看到这样的字眼对网络流熟悉一些的同学应该不难想到二分图匹配,具体来说我们将每个点裂成两个,对于每条原图中的边 \(u\to v\) 看作一条左边第 \(u\) 个点连向右边第 \(v\) 个点的边,那么 \(B\) 每列上的元素对应的边构成了一个匹配。这样一来就好办了,我们只要将原二分图的边集拆成 \(m\) 个匹配即可。我们考虑每次都找到一个匹配并将这些边从原图中删掉,可以证明每次删掉一个匹配后,都还能找到一个完美匹配。具体证明大概是由于原图是一个 \(n\) 阶正则图,可以考虑反证法,假设 \(\exists V\in\text{Left},s.t.|\text{Nb}(V)|<|V|\),那么 \(|\text{Nb}(V)|\) 与 \(|V|\) 之间边数应为 \(n|V|\),根据抽屉原理必然 \(\exists u\in\text{Nb}(V),s.t.\text{deg}(u)>n\),矛盾。这样也变相说明了 \(k\) 阶正则二分图必然存在完美匹配。

时间复杂度大概是 \(n^{3.5}\),如果 \(n,m\) 视作同阶。

const int MAXN=100;
const int MAXV=202;
const int MAXE=1e5;
const int INF=0x3f3f3f3f;
int n,m,a[MAXN+5][MAXN+5],b[MAXN+5][MAXN+5],c[MAXN+5][MAXN+5],used[MAXN*MAXN+5];
int S,T,hd[MAXV+5],to[MAXE+5],nxt[MAXE+5],cap[MAXE+5],ec=1,eid[MAXE+5];
void adde(int u,int v,int f,int id){
// printf("%d %d %d %d\n",u,v,f,id);
to[++ec]=v;cap[ec]=f;nxt[ec]=hd[u];hd[u]=ec;eid[ec]=id;
to[++ec]=u;cap[ec]=0;nxt[ec]=hd[v];hd[v]=ec;eid[ec]=id;
} int dep[MAXV+5],now[MAXV+5];
bool getdep(){
memset(dep,-1,sizeof(dep));dep[S]=0;
queue<int> q;q.push(S);now[S]=hd[S];
while(!q.empty()){
int x=q.front();q.pop();
for(int e=hd[x];e;e=nxt[e]){
int y=to[e],z=cap[e];
if(z&&!~dep[y]){
dep[y]=dep[x]+1;
now[y]=hd[y];q.push(y);
}
}
} return ~dep[T];
}
int getflow(int x,int f){
if(x==T) return f;int ret=0;
for(int &e=now[x];e;e=nxt[e]){
int y=to[e],z=cap[e];
if(z&&dep[y]==dep[x]+1){
int w=getflow(y,min(f-ret,z));
ret+=w;cap[e]-=w;cap[e^1]+=w;
if(f==ret) return ret;
}
} return ret;
}
int dinic(){
int ret=0;
while(getdep()) ret+=getflow(S,INF);
return ret;
}
int main(){
scanf("%d%d",&n,&m);T=(S=n<<1|1)+1;
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) scanf("%d",&a[i][j]);
for(int i=1;i<=m;i++){
memset(hd,0,sizeof(hd));ec=1;
for(int x=1;x<=n;x++) for(int y=1;y<=m;y++) if(!used[(x-1)*m+y]){
int should=(a[x][y]+m-1)/m;adde(x,should+n,1,(x-1)*m+y);
} for(int j=1;j<=n;j++) adde(S,j,1,0),adde(j+n,T,1,0);
assert(dinic()==n);
for(int j=1;j<=n;j++) for(int e=hd[j];e;e=nxt[e]){
if(to[e]!=S&&cap[e^1]){
int id=eid[e],x=(id+m-1)/m,y=(id-1)%m+1;
b[j][i]=c[to[e]-n][i]=a[x][y];used[id]=1;
// printf("used %d %d %d\n",id,i,e);
}
}
}
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) printf("%d%c",b[i][j]," \n"[j==m]);
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) printf("%d%c",c[i][j]," \n"[j==m]);
return 0;
}

AGC 043 D

一道挺有意思的数数题。

首先碰到这样的问题我们肯定要考虑什么样的排列符合要求。我们考虑 \(n\) 的位置——显然只有在迫不得已,即另外 \(n-1\) 个指针都已经被摧毁的时候才会将 \(n\) 加入序列,因此在 \(n\) 后面的元素肯定跟 \(n\) 是在一组的,我们完全可以把这些元素捆在一起,显然如果 \(n\) 后面元素个数 \(\ge 4\) 就直接不合法了,否则我们考虑删除这些元素,接着考虑序列中最大的元素,依次类推下去即可。因此对于一个排列 \(p\),它可以成为合法的排列的必要条件是:如果我们每次找出序列最大的元素并将其后面的元素全部删除,那么每次删除的元素个数 \(\le 3\)。为什么说是“必要条件”呢?因为考虑排列 \(p=(2,1,4,3,6,5)\),虽然它每次删除的数的个数都 \(\le 3\)(\(=2\)),但就是无法找到某个排列 \(p'\) 使得 \(p'\) 能够生成 \(p\)。因为我们不仅要将这些元素捆在一起,还要将它们组成若干个三元组,而三个 \(2\) 无法通过组合变成两个 \(3\),因此不可行。所以我们还要使拆出的数能够构成若干个三元组,\(3\) 自然可以单独成一组,我们 duck 不必管它,\(2\) 显然只能和 \(1\) 配对,如果 \(2\) 的个数严格多于 \(1\) 的个数那就会存在多余的 \(2\),也就不合法了,因此我们还要让拆出来的 \(2\) 的个数 \(\le 1\) 的个数。

到这里此题排列 \(p\) 的性质都已经分析完了,我们先考虑如何暴力地求答案,以 \(n=2\) 为例,我们枚举拆出的每段按顺序的值分别是多少,显然共有 \(1,1,1,1,1,1\)、\(1,1,1,1,2\)、\(1,1,1,2,1\)、\(1,1,2,1,1\)、\(1,2,1,1,1\)、\(2,1,1,1,1\)、\(1,1,1,3\)、\(1,1,3,1\)、\(1,3,1,1\)、\(3,1,1,1\),\(1,1,2,2\)、\(1,2,1,2\)、\(1,2,2,1\)、\(2,1,1,2\)、\(2,1,2,1\)、\(2,2,1,1\)、\(1,2,3\)、\(1,3,2\)、\(2,1,3\)、\(2,3,1\)、\(3,1,2\)、\(3,2,1\)、\(3,3\) 这些种可能,考虑怎样计算每一种可能的答案,以 \(1,3,2\) 为例,显然由于最后一组为 \(2\),因此 \(6\) 必须填第 \(5\) 个位置,这样一来最后一个元素就有 \(5\) 种可能,确定最后一个元素后,剩余 \(4\) 个元素中最大值的位置就定下来了,剩下来 \(a_3,a_4\) 还有 \(A_{3}^2=6\) 种可能,第一个元素也就只有 \(1\) 种选择了,所以这种情况的贡献为 \(30\),建议读者手算一下 \(n=2\) 的情况,看看是不是 \(261\),虽然有点烦但对正解有很强的启发性作用。

相信通过上面手算的过程我们可以看出,假设从左到右每一段长度分别为 \(a_1,a_2,\cdots,a_k\),那么方案数就是 \(\dfrac{n!}{a_1(a_1+a_2)(a_1+a_2+a_3)\cdots(a_1+a_2+\cdots+a_k)}\),这样就可以 \(dp\) 了,\(dp_{i,j}\) 表示考虑到第 \(i\) 个元素,\(1\) 的个数减去 \(2\) 的个数等于 \(j\) 的方案数,转移显然可以 \(\mathcal O(1)\),总复杂度 \(n^2\)。

const int MAXN=6000;
const int DELTA=6002;
int n,mod,ans=0;
int dp[MAXN+5][MAXN*2+5];
void add(int &x,int v){((x+=v)>=mod)&&(x-=mod);}
int main(){
scanf("%d%d",&n,&mod);n*=3;dp[0][DELTA]=1;
for(int i=0;i<n;i++) for(int j=DELTA-n;j<=DELTA+n;j++) if(dp[i][j]){
add(dp[i+1][j+1],dp[i][j]);
if(i+2<=n) add(dp[i+2][j-1],1ll*dp[i][j]*(i+1)%mod);
if(i+3<=n) add(dp[i+3][j],1ll*dp[i][j]*(i+1)%mod*(i+2)%mod);
} for(int i=DELTA;i<=DELTA+n;i++) add(ans,dp[n][i]);
printf("%d\n",ans);
return 0;
}

当然 lxr 总有比我更 nb 的做法,我们不考虑什么 DP,直接枚举 \(2,3\) 的个数计算方案数,假设 \(1,2,3\) 个数分别为 \(c_1,c_2,c_3\),那么显然对于每个块我们给它们填上数的方案数是一个多重组合数的形式,即 \(\dbinom{n}{1,1,\cdots,1,2,2,\cdots,2,3,3,\cdots,3}\),其中下面 \(1,2,3\) 分别是 \(c_1,c_2,c_3\) 个,套用多重组合数公式算一下是 \(\dfrac{n!}{2^{c_2}6^{c_3}}\),那么显然我们给这些块填好数后,我们肯定会按照最大数的大小从小到大将这些块排成一列,当然所有 \(1,2,3\) 都视作相同元素,因此还需除以 \(\dfrac{1}{c_1!c_2!c_3!}\)。对于长度为 \(3\) 的块还有 \(3,1,2\) 和 \(3,2,1\) 两种填数的方法,因此还要乘上 \(2^{c_3}\),故对于某对 \(c_1,c_2,c_3\),填数的方案数就是 \(\dfrac{n!}{c_1!c_2!c_3!2^{c_2}3^{c_3}}\),这个随便算算就行了。

不过似乎这个做法依赖于 \(m\) 是质数?

代码?sorry,暂(yong)时(jiu)没有代码,不过代码难度这么低的题为什么还要参考别人的代码呢?

AGC 050 D

一个垃圾的 \(\mathcal O(n^5)\) 的解法。

首先一件非常显然的事情是游戏最多进行 \(k\) 轮,因为 \(k\) 轮以后所有物品的位置都已经知道了。而且对于一个人 \(i\),如果它到了第 \(j\) 轮还没有结束游戏,那么必然前 \(j-1\) 轮随机点到的物品都被抢过了,因此如果假设第 \(j\) 轮已经有 \(l\) 个物品被抢过了,那么第 \(j\) 轮 \(i\) 抢到物品的概率就是 \(\dfrac{k-l}{k-j+1}\)。

有了这个性质之后考虑怎样计算答案。首先我们肯定要对所有人计算答案对吧,我们假设现在要对 \(i\) 计算答案,由于抢物品有先后顺序,我们仅仅记录每一轮有多少个物品被抢走,也即有多少个人抢到了物品是不够的,不过注意到在 \(i\) 前面和在 \(i\) 后面的人实际上都是等价的,因此我们只需记录 \(i\) 前面和 \(i\) 后面分别由多少个人拿到了物品即可确定每一步拿到物品的概率。因此考虑设 \(dp_{j,l,x,y}\) 表示当前考虑到了第 \(j\) 轮,现在第 \(j\) 轮中前 \(l\) 个人已经随机选择了物品,\(i\) 前面和后面分别有 \(x\) 个人和 \(y\) 个人拿到了物品的概率,转移就 xjb 分类讨论即可。

时间复杂度 \(\mathcal O(n^5)\)

并不知道你们所说的 \(\mathcal O(n^4)\) 的解法是啥

const int MAXN=40;
const int MOD=998244353;
int n,k,inv[MAXN+5];
int dp[MAXN+5][MAXN+5][MAXN+5][MAXN+5];
int main(){
scanf("%d%d",&n,&k);
for(int i=(inv[0]=inv[1]=1)+1;i<=MAXN;i++) inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
for(int i=1;i<=n;i++){
memset(dp,0,sizeof(dp));dp[0][n][0][0]=1;int res=0;
for(int j=1;j<=k;j++){
for(int x=0;x<=i-1;x++) for(int y=0;y<=n-i;y++)
dp[j][0][x][y]=dp[j-1][n][x][y];
for(int l=1;l<=n;l++){
for(int x=0;x<=i-1;x++) for(int y=0;y<=n-i;y++){
int p=1ll*(k-x-y)*inv[k-j+1]%MOD;
// printf("%d %d %d %d %d\n",j,l-1,x,y,dp[j][l-1][x][y]);
if(l==i){
dp[j][l][x][y]=(dp[j][l][x][y]+1ll*dp[j][l-1][x][y]*(1-p+MOD))%MOD;
res=(res+1ll*dp[j][l-1][x][y]*p)%MOD;
} else if(l<i){
if(l<=x) dp[j][l][x][y]=(dp[j][l][x][y]+dp[j][l-1][x][y])%MOD;
else{
dp[j][l][x+1][y]=(dp[j][l][x+1][y]+1ll*dp[j][l-1][x][y]*p)%MOD;
dp[j][l][x][y]=(dp[j][l][x][y]+1ll*dp[j][l-1][x][y]*(1-p+MOD))%MOD;
}
} else {
if(l-i<=y) dp[j][l][x][y]=(dp[j][l][x][y]+dp[j][l-1][x][y])%MOD;
else{
dp[j][l][x][y+1]=(dp[j][l][x][y+1]+1ll*dp[j][l-1][x][y]*p)%MOD;
dp[j][l][x][y]=(dp[j][l][x][y]+1ll*dp[j][l-1][x][y]*(1-p+MOD))%MOD;
}
}
}
}
} printf("%d\n",res);
}
return 0;
}

三个 AGC D(AGC037D、AGC043D、AGC050D)的更多相关文章

  1. AtCoder Beginner Contest 122 D - We Like AGC (DP)

    D - We Like AGC Time Limit: 2 sec / Memory Limit: 1024 MB Score : 400400 points Problem Statement Yo ...

  2. AGC 018E.Sightseeing Plan——网格路径问题观止

    原题链接 鸣谢:AGC 018E.Sightseeing Plan(组合 DP) 本蒟蒻认为,本题堪称网格路径问题观止. 因为涵盖了不少网格路径问题的处理方法和思路. 一句话题意: 给你三个矩形. 三 ...

  3. RE:从零开始的AGC被虐(到)生活(不能自理)

    RE:从零开始的AGC被虐(到)生活(不能自理) 「一直注视着你,似近似远,总是触碰不到.」 --来自风平浪静的明天 AtCoder Grand Contest 001 B: Mysterious L ...

  4. 三个小白是如何在三个月内搭一个基于kaldi的嵌入式在线语音识别系统的

    前面的博客里说过最近几个月我从传统语音(语音通信)切到了智能语音(语音识别).刚开始是学语音识别领域的基础知识,学了后把自己学到的写了PPT给组内同学做了presentation(语音识别传统方法(G ...

  5. 详解 WebRTC 高音质低延时的背后 — AGC(自动增益控制)

    前面我们介绍了 WebRTC 音频 3A 中的声学回声消除(AEC:Acoustic Echo Cancellation)的基本原理与优化方向,这一章我们接着聊另外一个 "A" - ...

  6. 【AGC】引导用户购买提升用户留存率

    借助AGC的云数据库.云托管.应用内消息.App Linking等服务,您可以给不同价值用户设置不同的优惠套餐活动,引导用户持续购买,增强用户黏性.判断用户价值,发送营销短信,引导用户参与营销活动,提 ...

  7. 常用 Gulp 插件汇总 —— 基于 Gulp 的前端集成解决方案(三)

    前两篇文章讨论了 Gulp 的安装部署及基本概念,借助于 Gulp 强大的 插件生态 可以完成很多常见的和不常见的任务.本文主要汇总常用的 Gulp 插件及其基本使用,需要读者对 Gulp 有一个基本 ...

  8. 【原】FMDB源码阅读(三)

    [原]FMDB源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 FMDB比较优秀的地方就在于对多线程的处理.所以这一篇主要是研究FMDB的多线程处理的实现.而 ...

  9. Jquery的点击事件,三句代码完成全选事件

    先来看一下Js和Jquery的点击事件 举两个简单的例子 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN&q ...

随机推荐

  1. nexus设置npm下载管理

    nexus设置npm下载管理 第一步 登录私服网页 第二步 创建存储空间(如果使用默认的存储空间,此步骤可省略) 第三步 输入空间的名称,点击create创建 第四步 创建仓库 npm的仓库有三种: ...

  2. 提高微信小程序的应用速度

    一.是什么 小程序启动会常常遇到如下图场景: 这是因为,小程序首次启动前,微信会在小程序启动前为小程序准备好通用的运行环境,如运行中的线程和一些基础库的初始化 然后才开始进入启动状态,展示一个固定的启 ...

  3. vue.$nextTick实现原理

    源码: const callbacks = [] let pending = false function flushCallbacks () { pending = false const copi ...

  4. Spring Cloud Alibaba整合Sentinel

    Spring Cloud Alibaba 整合 Sentinel 一.需求 二.实现步骤 1.下载 sentinel dashboard 2.服务提供者和消费者引入sentinel依赖 3.配置控制台 ...

  5. 必备的60个常用的Linux命令

    Linux必学的60个命令Linux提供了大量的命令,利用它可以有效地完成大量的工 作,如磁盘操作.文件存取.目录操作.进程管理.文件权限设定等.所以,在Linux系统上工作离不开使用系统提供的命令. ...

  6. 数据治理之元数据管理的利器——Atlas入门宝典

    随着数字化转型的工作推进,数据治理的工作已经被越来越多的公司提上了日程.作为Hadoop生态最紧密的元数据管理与发现工具,Atlas在其中扮演着重要的位置.但是其官方文档不是很丰富,也不够详细.所以整 ...

  7. .NET 5 全自动分表组件,.NET 分表方案 ,分表架构与设计

    一.疑问&目的 1.1 分表使用场景 (1)可扩展架构设计,比如一个ERP用5年不卡,到了10就卡了因为数据太多了,这个时候很多人都是备份然后清空数据,这个工作大并且麻烦,以前的数据很难在使用 ...

  8. linux wifi热点服务脚本

    最近有关wifi热点的驱动,启动参数都调试完了,验证可以连接传输数据. 首先要在系统启动脚本中插入wifi驱动,配置wlan0的ip insmod /system/vendor/modules/818 ...

  9. SpirngBoot整合Mybatis Plus多数据源

    导读 有一个这样子的需求,线上正在跑的业务,由于业务发展需要,需重新开发一套新系统,等新系统开发完成后,需要无缝对接切换,当初具体设计见草图. 添加依赖 <!--lombok--> < ...

  10. 关于dns服务工作的原理,和配置的细节理解。

    dns服务器相关 1,dns原理,也就是迭代,和递归查询.将域名解析为ip的过程. 一次完整的查询请求经过的流程: Client -->hosts文件 -->DNS Service Loc ...