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\)的方案数,不难发现我们有下面三种转移:

  1. \(x,d\)\(1\),表示较远的那个向前走了一步
  2. \(d\)减1,满足\(d\ge 2\),表示较远的那个向前走了一步,置于为什么要\(d\ge 2\)是为了在\(d=1\)时与第三种操作区分(因为它不是数操作方案构成的序列数)
  3. \(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真是发人深省呢……

posted @ 2019-11-28 20:53  空気力学の詩  阅读(219)  评论(0编辑  收藏  举报