AtCoder Grand Contest 040
Preface
今年准备省选啥都不说了,省选题基本上都做过一遍了,开始尝试板刷AGC
这场做完就从AGC001开始吧,感觉以我的速度和来机房的频率一个礼拜做一场都谢天谢地了
A - ><
考虑我们找出所有的山谷的位置,把它们设成0
然后它有两种向两边扩展方式,直接取\(\min\)BFS一下就好了,显然状态数是\(2n\)的
#include<cstdio>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=500005;
int n,q[N],val[N]; char s[N]; long long ans;
int main()
{
RI i,H=0,T=0; scanf("%s",s+1); n=strlen(s+1)+1;
for (i=1;i<=n;++i) if ((s[i-1]!='<')&&(s[i]!='>')) q[++T]=i;
while (H<T)
{
int now=q[++H];
if (now>1&&s[now-1]=='>'&&val[now-1]<val[now]+1) val[now-1]=val[now]+1,q[++T]=now-1;
if (now<n&&s[now]=='<'&&val[now+1]<val[now]+1) val[now+1]=val[now]+1,q[++T]=now+1;
}
for (i=1;i<=n;++i) ans+=val[i]; return printf("%lld",ans),0;
}
B - Two Contests
本以为我的做法挺妙的了然后陈指导给我看了他更妙的代码窝甘拜下风
首先我们找出两个区间\(p,q\),其中\(p\)是\(l\)最大的区间,\(q\)是\(r\)最小的区间
考虑两种情况,如果将\(p,q\)置于同一集合,那么显然此时它们之间的答案已经不可能再小了,直接从剩下的区间里挑一个最长的放在另一个集合里
否则考虑将\(p,q\)分开,此时我们的答案显然是这样(设\(p\in A,q\in B\))
\((\min_{i\in A} r_i-l_p)+(r_q-\max_{i\in B} l_i)\)
考虑将剩下的区间按\(l_i\)排序,然后处理出\(r_i\)的后缀最小值即可计算答案
注意特判\(n=2\)的情况
#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005,INF=1e9;
struct interval
{
int l,r;
friend inline bool operator < (const interval& A,const interval& B)
{
return A.l<B.l;
}
}a[N],b[N]; int n,mxl=-1,mir=INF,mlen,tot,p,q,sufr[N],ans;
int main()
{
RI i; for (scanf("%d",&n),i=1;i<=n;++i)
{
scanf("%d%d",&a[i].l,&a[i].r);
if (a[i].l>mxl) mxl=a[i].l,p=i; if (a[i].r<mir) mir=a[i].r,q=i;
}
if (n==2) return printf("%d",a[1].r-a[1].l+1+a[2].r-a[2].l+1),0;
for (i=1;i<=n;++i) if (i!=p&&i!=q) b[++tot]=a[i],mlen=max(mlen,a[i].r-a[i].l+1);
for (sort(b+1,b+tot+1),i=tot-1,sufr[tot]=b[tot].r;i>0;--i) sufr[i]=min(sufr[i+1],b[i].r);
for (ans=mlen+max(mir-mxl+1,0),i=1;i<tot;++i)
ans=max(ans,max(mir-b[i].l+1,0)+max(sufr[i+1]-mxl+1,0));
ans=max(ans,a[q].r-a[q].l+1+max(sufr[1]-mxl+1,0));
ans=max(ans,max(mir-b[tot].l+1,0)+a[p].r-a[p].l+1);
return printf("%d",ans),0;
}
C - Neither AB nor BA
大脑短路卡在算答案的地方结果愣是花了两小时QAQ
首先我们搞一个转化,把所有偶数位上的字符都变换一下,即\(A\to B;B\to A;C\to C\),显然我们统计出转化后的字符串个数就是原来的答案(翻转是唯一对应的)
考虑这个时候我们的操作变成了不能删去连续的两个\(A\)或两个\(B\)
先不考虑\(C\),那么我们现在每次都是删除一个\(A\)和一个\(B\)
首先我们发现他成立的必要条件是\(A\)的个数等于\(B\)的个数,然后稍加分析发现它也是充分的
然后考虑有\(k\)个\(C\),由于每个\(C\)都可以让它带走一个\(A\)或\(B\)(我们总是选择带走数量多的那个),因此此时满足\(|N_A-N_B|\le N_C\)(\(N_A\)表示\(A\)的个数)
然后我就码出了一个\(n^2\)暴力,然后和陈指导找了一个多小时的规律在系数上(智力退化严重)
后来瞄了一眼题解发现只要容斥一下就好了,因为我们总是把\(C\)当做数量少的那种字符来用,因此如果多的那种字符数目超过了\(\frac{n}{2}\)就一定不合法
那么枚举多的数目直接统计即可
#include<cstdio>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=1e7+5,mod=998244353;
int n,ret,fact[N],inv[N],pw[N];
inline void inc(int& x,CI y)
{
if ((x+=y)>=mod) x-=mod;
}
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void init(CI n)
{
RI i; for (fact[0]=i=1;i<=n;++i) fact[i]=1LL*fact[i-1]*i%mod;
for (inv[n]=quick_pow(fact[n]),i=n-1;~i;--i) inv[i]=1LL*inv[i+1]*(i+1)%mod;
for (pw[0]=i=1;i<=n;++i) pw[i]=2LL*pw[i-1]%mod;
}
inline int C(CI n,CI m)
{
return 1LL*fact[n]*inv[m]%mod*inv[n-m]%mod;
}
int main()
{
RI i,j; for (scanf("%d",&n),init(n),i=(n>>1)+1;i<=n;++i)
inc(ret,1LL*C(n,i)*pw[n-i+1]%mod);
return printf("%d",(quick_pow(3,n)-ret+mod)%mod),0;
}
D - Balance Beam
妈呀不会,D感觉比E还难啊(窝好菜啊)
设\(sum=\sum_{i=1}^n \max(b_i-a_i,0)\)。首先我们假设已经知道了顺序,考虑一种贪心:
for (i=1;i<=n;++i)
if (sum>=b[i]) sum-=b[i],++ans; else { ans+=1.0*sum/b[i]; break; }
那么我们考虑枚举进入else
语句里的\(i\)来算答案,我们记\(b_i-a_i\)的前缀\(\max\)位置为\(pmx\),因此贡献被分为\([1,i),(i.pmx],(pmx,n]\)三部分
然后我们发现如果我们把所有\(a_i<b_i\)的beam加到\((i,pmx]\)时,然后把多出的放到\(i\)前面,不难发现放到前面的代价是\(\max(a_i,b_i)\)
因此我们先按\(\max(a_i,b_i)\)排个序之后每次枚举\(i\)是尽量让前面的来的大,那么直接二分取得了整的前缀长度即可
#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
#define int long long
using namespace std;
const int N=100005;
struct frac
{
int x,y;
inline frac(CI X=0,CI Y=0)
{
x=X; y=Y;
}
friend inline bool operator < (const frac& A,const frac& B)
{
return 1.0*A.x*B.y<1.0*A.y*B.x;
}
friend inline bool operator > (const frac& A,const frac& B)
{
return 1.0*A.x*B.y>1.0*A.y*B.x;
}
}ans(0,1);
struct data
{
int a,b,val;
friend inline bool operator < (const data& A,const data& B)
{
return A.val<B.val;
}
}p[N]; int pfx[N],n,sum;
inline int gcd(CI x,CI y)
{
return y?gcd(y,x%y):x;
}
signed main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i; for (scanf("%lld",&n),i=1;i<=n;++i)
scanf("%lld%lld",&p[i].a,&p[i].b),
p[i].val=max(p[i].a,p[i].b),sum+=max(p[i].b-p[i].a,0LL);
for (sort(p+1,p+n+1),i=1;i<=n;++i) pfx[i]=pfx[i-1]+p[i].val;
#define calc(x) (pfx[x]-((x)>=i?p[i].val:0))
for (i=1;i<=n;++i)
{
int cur=sum; if (p[i].a>p[i].b) cur+=p[i].b-p[i].a;
int l=1,r=n,mid; while (l<=r)
if (calc(mid=l+r>>1)<=cur) l=mid+1; else r=mid-1;
//printf("%lld %lld\n",p[i].a,p[i].b);
frac ret(cur-calc(l-1),p[i].b); ret=min(ret,frac(1,1));
ret.x+=ret.y*(l-1-(l>i)); ans=max(ans,ret);
}
#undef calc
int g=gcd(ans.x,ans.y*=n); return printf("%lld %lld",ans.x/g,ans.y/g),0;
}
E - Prefix Suffix Addition
个人感觉比D清新多了,首先我们发现可以把两种操作分开来考虑
假设我们现在只考虑第一种操作,那么我们一定存在一种方案,使得所有非\(0\)的位置不相交(证明非常简单,如果有一种相交的方案那么一定一个把某一部分加到另一个上面去)
假设现在我在第\(i\)个位置上填了\(x_i\),那么产生贡献就是\(x_{i+1}<x_i\)时
那么我们类推第二种操作,设它填的数为\(y_i\),那么现在问题变成了:
满足\(i\in [1,n],x_i+y_i=a_i\)时
最小化\(\sum_{i=1}^n [x_{i+1}<x_i]+[y_{i+1}>y_i]\)
我们容易搞出一个DP,令\(f_{i,j}\)表示第\(i\)个位置\(x_i=j\)的最小代价,然后我们可以找出一些性质:
- \(f_{i,j},j\in [0,a_i]\)单调不增,这个看一下算贡献的式子就能发现
- \(f_{i,j},j\in[0,a_i]\)的极差\(\le 2\)
那么也就意味着我只要知道\(f_{i,0}\),然后记录下\(f_i\)的两个分界点即可
转移的时候也很简单,求\(f_i\)的分界点时只需要二分一下,然后\(f_{i-1}\)的肯定是尽量取小,那么取分界点讨论一下贡献即可
复杂度\(O(n\log n)\),据说有\(O(n)\)的做法(瑟瑟发抖)
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
struct data
{
int p1,p2,val,a;
inline data(CI P1=0,CI P2=0,CI Val=0,CI A=0)
{
p1=P1; p2=P2; val=Val; a=A;
}
#define calc(lstx) ((lstx>nx)+(a-lstx<na-nx))
inline int get(CI na,CI nx)
{
int ret=val+calc(0); if (p1<=a) ret=min(ret,val-1+calc(p1));
if (p2<=a) ret=min(ret,val-2+calc(p2)); return ret;
}
#undef calc
}f[N]; int n;
int main()
{
RI i; for (scanf("%d",&n),f[0]=data(1,1),i=1;i<=n;++i)
{
scanf("%d",&f[i].a); f[i].val=f[i-1].get(f[i].a,0); int l,r,mid;
l=1; r=f[i].a; while (l<=r) if (f[i-1].get(f[i].a,mid=l+r>>1)==f[i].val)
l=mid+1; else r=mid-1; f[i].p1=l;
r=f[i].a; while (l<=r) if (f[i-1].get(f[i].a,mid=l+r>>1)==f[i].val-1)
l=mid+1; else r=mid-1; f[i].p2=l;
}
return printf("%d",f[n].get(0,0)),0;
}
F - Two Pieces
妙啊!可惜我当然是想不出来的(妈耶一种基本情况我想了快半小时才想通)
考虑把问题抽象化,由于两个碎片不可区分,因此我们可以设一个状态\((x,d)\)表示走的远的那个碎片在\(x\),另一个碎片距离它\(d\)的方案数,不难发现我们有下面三种转移:
- \(x,d\)加\(1\),表示较远的那个向前走了一步
- \(d\)减1,满足\(d\ge 2\),表示较远的那个向前走了一步,置于为什么要\(d\ge 2\)是为了在\(d=1\)时与第三种操作区分(因为它不是数操作方案构成的序列数)
- \(d=0\),表示把两个移到了一起
考虑我们先确定前面两种的放置方案数,然后插入第三种操作
不难发现第一种操作恰好被执行了\(B\)次,因此枚举第二种操作次数\(k\)
如果\(n=B+k\),那么我们现在相当于一个坐标系上的走路问题
显然这里第一步必须向右上走,然后我们把向下走转化成向右下走,限制变成不能碰到\(x\)轴,然后不合法的沿\(x\)轴翻转一下就可以统计方案数了(其实就是类Catalan数的统计方法)
否则剩下\(n-B-k\)个操作都是第三类,考虑它插入之后必须满足
- 纵坐标能到达\(B-A\)
- 不会使原来合法的第二种操作不合法
考虑它的影响相当于把一段后缀的\(d\)减去其中第一个\(d\)的值,因此这个位置必须是严格的后缀最小值
然后还要满足第一个限制,我们发现最后一次操作要满足此时的纵坐标为\(A-k\)
然后满足这些之后剩下的\(n-B-1-k\)个操作可以在所有\(d\le A-k\)的严格后缀最小值处插入,然后分析一下\(d\)的性质就会发现每个值都有且只有一个后缀最小值的位置
因此我们再用经典的隔板法计算一下方案数即可,综上便解决了这题
#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=1e7+5,mod=998244353;
int n,a,b,fact[N<<1],inv[N<<1],ans;
inline void inc(int& x,CI y)
{
if ((x+=y)>=mod) x-=mod;
}
inline int sub(CI x,CI y)
{
int t=x-y; return t<0?t+mod:t;
}
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void init(CI n)
{
RI i; for (fact[0]=i=1;i<=n;++i) fact[i]=1LL*fact[i-1]*i%mod;
for (inv[n]=quick_pow(fact[n]),i=n-1;~i;--i) inv[i]=1LL*inv[i+1]*(i+1)%mod;
}
inline int C(CI n,CI m)
{
if (n<m) return 0; return 1LL*fact[n]*inv[m]%mod*inv[n-m]%mod;
}
int main()
{
RI i; scanf("%d%d%d",&n,&a,&b); if (!b) return puts("1"),0;
for (init(n<<1),i=0;i<=n-b&&i<=a;++i)
{
int ret=sub(C(b-1+i,b-1),C(b-1+i,b));
if (i==n-b) inc(ans,i==a?ret:0); else inc(ans,1LL*ret*C(n-b-i-1+a-i,a-i)%mod);
}
return printf("%d",ans),0;
}
Postscript
AGC真是发人深省呢……
AtCoder Grand Contest 040的更多相关文章
- 【AtCoder】AtCoder Grand Contest 040 解题报告
点此进入比赛 \(A\):><(点此看题面) 大致题意: 给你一个长度为\(n-1\).由\(<\)和\(>\)组成的的字符串,第\(i\)位的字符表示第\(i\)个数和第\( ...
- AtCoder Grand Contest 040 简要题解
从这里开始 比赛目录 A < B < E < D < C = F,心情简单.jpg. Problem A >< 把峰谷都设成 0. Code #include &l ...
- AtCoder Grand Contest 040 C - Neither AB nor BA
传送门 好妙的题啊 首先容易想到简单容斥,统计合法方案数可以考虑总方案数减去不合法方案数 那么先考虑如何判断一个串是否合法,但是直接判断好像很不好搞 这时候就需要一些 $magic$ 了,把所有位置下 ...
- AtCoder Grand Contest 040 B - Two Contests
传送门 一看就感觉很贪心 考虑左端点最右的区间 $p$ 和右端点最左的区间 $q$ 如果 $p,q$ 属于同一个集合(设为 $S$,另一个集合设为 $T$),那么其他的区间不管是不是在 $S$ 都不会 ...
- AtCoder Grand Contest 040 A - ><
传送门 对于某个位置,只要知道这个位置往左最多的连续 $\text{<}$ 的数量 $x$ 和往右最多的连续 $\text{>}$ 的数量 $y$ 那么这个位置最小可能的数即为 $max( ...
- AtCoder Grand Contest 012
AtCoder Grand Contest 012 A - AtCoder Group Contest 翻译 有\(3n\)个人,每一个人有一个强大值(看我的假翻译),每三个人可以分成一组,一组的强大 ...
- AtCoder Grand Contest 011
AtCoder Grand Contest 011 upd:这篇咕了好久,前面几题是三周以前写的... AtCoder Grand Contest 011 A - Airport Bus 翻译 有\( ...
- AtCoder Grand Contest 031 简要题解
AtCoder Grand Contest 031 Atcoder A - Colorful Subsequence description 求\(s\)中本质不同子序列的个数模\(10^9+7\). ...
- AtCoder Grand Contest 010
AtCoder Grand Contest 010 A - Addition 翻译 黑板上写了\(n\)个正整数,每次会擦去两个奇偶性相同的数,然后把他们的和写会到黑板上,问最终能否只剩下一个数. 题 ...
随机推荐
- document.write() 为什么会清空页面
很久以前遇到的问题,放着放着就忘记去研究了最近看到一篇文章总结一下作者:abloumeurl: http://blog.csdn.net/u013451157/article/details/78 ...
- java读取文本文件内容2
版权声明:本文为xing_star原创文章,转载请注明出处! 本文同步自http://javaexception.com/archives/183 很久之前写了一篇Java读取文本文件内容,链接地址是 ...
- s3c2440裸机-清bss原理及实现
1.清bss的引入(为什么要清bss) 我们先举个例子: #include "s3c2440_soc.h" #include "uart.h" char g_C ...
- python之os模块(os.path)
我们在做自动化测试的时候,可能会遇到一些需要处理文件一些需求,那么我们可以通过直接写文件的目录进行操作,当然作为一名自动化测试工程师,怎么可能用这种方法?python中自带的有OS,我们可以通过os模 ...
- Centos8 配置静态IP
安装centos 8之后,重启启动网络时,会出现以下报错 报错信息如下: Failed to start network.service: Unit network.service not found ...
- 05-Node.js学习笔记-第三方模块
5.1什么是第三方模块 别人写好的,具有特定功能的,我们能直接使用的模块即第三方模块,由于第三方模块通常都是由多个文件组成并且被放置在一个文件夹中,所以又名包. 第三方模块有两种存在形式 以js文件的 ...
- Hibernate session.save()实体类,主键增长问题
实体类如下: package com.wondersgroup.test.entity; import java.io.Serializable; import javax.persisten ...
- ch-0503
内容来源<算法竞赛进阶指南>date of submission:20191121tags:归排description modelling:你一定玩过八数码游戏,它实际上是在一个3*3的网 ...
- go语言之if语句和switch语句和循环语句
1.if语句 package main import ( "fmt" "io/ioutil" ) func main() { //流程控制 //使用常量定义一个 ...
- 【开发工具】本机安装的JDK8,启动IDEA2019没反应
问题描述 本来开发工具安装的是IDEA2018,有天用着用着突然崩溃了,重启后死活用不了.心血来潮下载了2019版本,顺利安装完,但是点击快捷方式启动的时候一直没反应.后来咨询同事,在下面的启动脚本中 ...