AtCoder Grand Contest 019

Preface

最后四天,最后四场!


A - Ice Tea Store

有手就行,考虑偶数部分和多出的一个\(0/1\)分别用什么买最优即可

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
int a,b,c,d,n;
int main()
{
	scanf("%d%d%d%d%d",&a,&b,&c,&d,&n);
	return printf("%lld",1LL*(n/2)*min(8*a,min(4*b,min(2*c,d)))+(n&1)*min(4*a,min(2*b,c))),0;
}

B - Reverse and Compare

刚开始想的贼复杂,后来被陈指导提示了才会做

考虑可操作的区间数量是\(\frac{n(n+1)}{2}+1\),我们考虑哪些区间是不合法的

直观地,若\([l,r]\)满足\(s_l=s_r\),那么他得到的结果和\([l+1,r-1]\)显然是相同的

因此我们对于每种字符求出它出现的个数统计一下对数即可

#include<cstdio>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int n,c[26]; char s[N]; long long ans;
int main()
{
	RI i; for (scanf("%s",s+1),n=strlen(s+1),i=1;i<=n;++i) ++c[s[i]-'a'];
	for (ans=1LL*n*(n+1)/2LL+1,i=0;i<26;++i) ans-=1LL*c[i]*(c[i]+1)/2LL;
	return printf("%lld",ans),0;
}

C - Fountain Walk

去年大概这个时候模拟赛考过,一年后再写感觉细节也挺少的

首先一个直观的想法,在直接走过去的路上走尽量多的\(\frac{1}{4}\)圆可以使路径长度减少\(20-5\pi\)

假设\(x_1<x_2,y_1<y_2\),我们把所有点都按\(x_i\)排序之后找出在两点之间的点,按\(y_i\)做一个LIS就是能经过的最多的\(\frac{1}{4}\)个数了,\(y_1\ge y_2\)同理

但是我们发现有些情况一定会经过半圆,画画图分析下此时若LIS长度\(<\min(|x_1-x_2|,|y_1-y_2|)+1\)那必然要经过一个半圆,多走\(10\pi -20\)的路

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
const double pi=acos(-1);
struct point
{
	int x,y;
	friend inline bool operator < (const point& A,const point& B)
	{
		return A.x<B.x;
	}
	inline void input(void) { scanf("%d%d",&x,&y); }
}p[N],s,t; int rst[N],n,ans,tp;
#define lowbit(x) (x&-x)
class Tree_Array1
{
	private:
		int bit[N];
	public:
		inline int get(RI x,int ret=0)
		{
			for (;x;x-=lowbit(x)) ret=max(ret,bit[x]); return ret;
		}
		inline void add(RI x,CI y)
		{
			for (;x<=n;x+=lowbit(x)) bit[x]=max(bit[x],y);
		}
}BIT1;
class Tree_Array2
{
	private:
		int bit[N];
	public:
		inline int get(RI x,int ret=0)
		{
			for (;x<=n;x+=lowbit(x)) ret=max(ret,bit[x]); return ret;
		}
		inline void add(RI x,CI y)
		{
			for (;x;x-=lowbit(x)) bit[x]=max(bit[x],y);
		}
}BIT2;
#undef lowbit
int main()
{
	RI i; s.input(); t.input(); if (s.x>t.x) swap(s,t);
	for (scanf("%d",&n),i=1;i<=n;++i) p[i].input(),rst[i]=p[i].y;
	for (sort(rst+1,rst+n+1),i=1;i<=n;++i) p[i].y=lower_bound(rst+1,rst+n+1,p[i].y)-rst;
	for (sort(p+1,p+n+1),i=1;i<=n;++i)
	if (p[i].x>=s.x&&p[i].x<=t.x&&rst[p[i].y]>=min(s.y,t.y)&&rst[p[i].y]<=max(s.y,t.y))
	{
		if (s.y<=t.y) ans=max(ans,tp=BIT1.get(p[i].y)+1),BIT1.add(p[i].y,tp);
		else ans=max(ans,tp=BIT2.get(p[i].y)+1),BIT2.add(p[i].y,tp);
	}
	double ret=100LL*(abs(s.x-t.x)+abs(s.y-t.y));
	if (ans<min(abs(s.x-t.x),abs(s.y-t.y))+1) printf("%.15lf",ret-ans*(20-5*pi));
	else printf("%.15lf",ret-(ans-1)*(20-5*pi)+10*pi-20); return 0;
}

D - Shift and Flip

都是陈指导指导的好,考虑一个\(O(n^3)\)暴力,枚举向左位移次数和向右位移次数,此时每个位置要变成什么已经确定了,预处理出每个位置左/右边最近的\(1\)再枚举每个位置判断下即可

然后我们发现每个位置最后变成什么之和左位移次数和向右位移次数的差值有关,因此我们考虑先枚举差值

接下来需要一点转化,因为直接枚举左位移次数不方便我们检验,我们枚举左位移次数的极值,即向左最远能到哪里

设每个位置\(i\)左/右边最近的\(1\)与该位置的距离为\(l_i,r_i\),此时枚举的左位移次数的极值为\(ml\),那么对于每个要修改的点只需要讨论下:

  • \(l_i\le ml\),这个点可以直接被改掉,不做处理
  • \(l_i>ml\),这个点现在只能在右边被改掉,那么相应的可以统计出此时右位移次数的极值\(mr=\max r_i\)

很显然把\((l_i,r_i)\)\(l_i\)排序再从大到小扫一边即可,但由于它们的值域也是\(O(n)\)的因此直接开个桶即可

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=2005;
int n,ans=1e9,l[N],r[N],v[N]; char a[N],b[N];
int main()
{
	RI i,j; scanf("%s%s",a,b); n=strlen(a);
	for (i=0;i<n;++i)
	{
		for (j=i;b[j]!='1'&&l[i]<n;j=(j-1+n)%n,++l[i]);
		for (j=i;b[j]!='1'&&r[i]<n;j=(j+1)%n,++r[i]);
		if (l[i]==n)
		{
			bool flag=1; for (j=0;j<n;++j) flag&=a[j]=='0';
			puts(flag?"0":"-1"); return 0;
		}
	}
	for (i=0;i<n;++i)
	{
		for (j=0;j<n;++j) v[j]=0; int ct=0;
		for (j=0;j<n;++j) if (a[j]!=b[(j+i)%n]) ++ct,v[l[j]]=max(v[l[j]],r[j]);
		int mr=0; for (j=n-1;~j;--j)
		{
			if (mr<=i) ans=min(ans,ct+2*j+i); else ans=min(ans,ct+2*j+mr+mr-i); mr=max(mr,v[j]);
		}
	}
	for (i=0;i<n;++i)
	{
		for (j=0;j<n;++j) v[j]=0; int ct=0;
		for (j=0;j<n;++j) if (a[j]!=b[(j-i+n)%n]) ++ct,v[l[j]]=max(v[l[j]],r[j]);
		int mr=0; for (j=n-1;~j;--j)
		{
			if (i>=j) ans=min(ans,ct+2*mr+i); else ans=min(ans,ct+2*mr+j+j-i); mr=max(mr,v[j]);
		}
	}
	return printf("%d",ans),0;
}

E - Shuffle and Swap

真神的一道题,被教育了

首先转化一下题目的要求,把题目看做让\(A\)中多余的\(1\)换到\(A\)中缺少\(1\)的地方去,具体地,我们定义:

  1. 公共点:满足\(A_i=1\land B_i=1\)。此时显然无论如何交换,该位置上的值都为\(1\)
  2. 起点:满足\(A_i=1\land B_i=0\)。此时我们需要把该位置上的\(1\)转移走
  3. 终点:满足\(A_i=0\land B_i=1\)。此时我们需要把该位置上转移来一个\(1\)

此时可以发现可以使得\(A=B\)的操作序列满足:

连接\((b_i,a_i)\),图的形态由若干由2,3类点作为端点,1类点作为中间点的有向链(即不能倒着操作一条链)

我们设\(f_{i,j}\)表示使用了\(i\)个公共点,组成了\(j\)条链的原序列(即\(a,b\))的方案数,发现转移:

  • 加入一个新的公共点,首先我们选取它所在的链的,并钦定它在末尾,在乘上标号,此时的贡献就是\(f_{i-1,j}\times i\times j\)
  • 加入一个新的链,还是钦定它放在末尾,考虑此时起点和终点的标号,由于是在原序列上,不难发现此时的贡献就是\(f_{i,j-1}\times j^2\)

由于每次转移会导致原序列的长度加\(1\),因此我们同样钦定每次新增后放在末尾

因为此时一条链可能会对应许多种原序列,我们要用不同的转移顺序来区分它们

接下来考虑统计答案,我们枚举公共点有多少个不放在链上,设一共有\(s\)个公共点,\(t\)条链,则:

\[ans=\sum_{i=0}^s C_s^i\times C_{s+t}^i\times (i!)^2\times f_{s-i,t} \]

总复杂度为\(O(\frac{|A|^2}{4})\),可以通过此题,多项式的做法就懒得写了(其实是不会)

#include<cstdio>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=10005,mod=998244353;
int n,f[N][N],fact[N],inv[N],s,t,ans; char a[N],b[N];
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)
{
	return 1LL*fact[n]*inv[m]%mod*inv[n-m]%mod;
}
int main()
{
	RI i,j; for (scanf("%s%s",a+1,b+1),n=strlen(a+1),i=1;i<=n;++i)
	if (a[i]=='1'&&b[i]=='1') ++s; else if (a[i]=='1'&&b[i]=='0') ++t;
	for (init(n),f[0][0]=1,i=0;i<=s;++i) for (j=0;j<=t;++j)
	(f[i][j]+=1LL*f[i-1][j]*i%mod*j%mod)%=mod,(f[i][j]+=1LL*f[i][j-1]*j%mod*j%mod)%=mod;
	for (i=0;i<=s;++i) (ans+=1LL*C(s,i)*fact[i]%mod*fact[i]%mod*C(s+t,i)%mod*f[s-i][t]%mod)%=mod;
	return printf("%d",ans),0;
}

F - Yes or No

好棒的一道题的说,又是熟悉的坐标系上走路问题

首先容易想到一个暴力DP,设\(f_{i,j}\)表示有\(n\)\(Yes\)\(m\)\(No\)情况下的答案,策略的话显然就是当前哪个剩下的多就选哪个,一样多就随便猜一个,容易得到转移:

  • \(f_{i,j}=\frac{n}{n+m}\times (f_{i-1,j}+1)+\frac{m}{n+m}\times f_{i,j-1}(i>j)\)
  • \(f_{i,j}=\frac{m}{n+m}\times (f_{i,j-1}+1)+\frac{n}{n+m}\times f_{i-1,j}(i<j)\)
  • 以上随便选一个,都是\(\frac{1}{2}\)的贡献\((i=j)\)

接下来我们考虑坐标系上走路,设此时一种回答的方案就是一条\((n,m)\)出发,走到\((0,0)\)的路径

考虑画出一条\(y=x\)的直线,结合上面的DP转移式发现此时直线上方的点都满足\(y>x\),此时只有纵向的路径是有贡献的;同理下方的点都满足\(y<x\),此时只有横向的路径是有贡献的

先考虑一种特殊情况,当\(n=m\)时,从\((n,n)\)走到\((0,0)\)的一条不经过\(y=x\)的路径,显然此时所有边的贡献都是确定的,会发现不管怎么走,最后的贡献一定是\(n\)

假定\(n\ge m\),我们考虑一条从\((n,m)\)走到\((0,0)\)的路径,按照触碰直线\(y=x\)的节点将这个路径划分为若干个部分

此时除了第一部分(即\((n,m)\)到第一个碰到的\(y=x\)上的点)的路径之外,其它的部分都可以看作从\(y=x\)上某一点出发,中途不经过\(y=x\),在\(y=x\)上某一点结束的路径

宣布让步除了第一部分之外的路径都和上面的\(n=m\)的情况是一样的,而在第一部分中,为了触碰\(y=x\),需要横着走\(n-m\)段,从而产生\(n-m\)的贡献,加上从\(y=x\)上开始走的贡献,此时我们发现确定的贡献即为\(\max(n,m)\)

接下来我们考虑真正受概率影响的点(即\(y=x\)上的点)的贡献怎么计算,显然只要有一条路径经过\(y=x\)上的某个点,不管它是横向的还是纵向的,都有\(\frac{1}{2}\)的概率获得\(1\)的贡献

因此我们只需要对对角线上的每个点计算出经过它的方案数,除以总路径数再乘上\(\frac{1}{2}\)就是这部分的贡献了

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=1000005,mod=998244353;
int n,m,fact[N],inv[N],ans;
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)
{
	return 1LL*fact[n]*inv[m]%mod*inv[n-m]%mod;
}
int main()
{
	RI i; for (scanf("%d%d",&n,&m),init(n+m),i=1;i<=min(n,m);++i)
	(ans+=1LL*C(2*i,i)*C(n+m-2*i,n-i)%mod)%=mod;
	ans=1LL*ans*quick_pow(C(n+m,n))%mod*quick_pow(2)%mod;
	return printf("%d",max(n,m)+ans),0;
}

Postscript

经典模型还是没做出来真屑啊

posted @ 2020-11-02 21:10  空気力学の詩  阅读(84)  评论(0编辑  收藏  举报