AtCoder Grand Contest 019 题解

传送门

(A)

咕咕

int a,b,c,d,n,t;
int main(){
	scanf("%d%d%d%d%d",&a,&b,&c,&d,&n);
	t=n&1,n-=t,a*=8,b*=4,c*=2;
	printf("%lld
",1ll*min(min(a,b),min(c,d))*(n>>1)+(min(min(a,b),c)>>1)*t);
	return 0;
}

(B)

先不考虑结果不变的操作,发现如果一个翻转的区间([l,r])满足(s[l]=s[r])那么我们现在可以选择操作([l+1,r-1])而使得最终的序列和翻转([l,r])之后的序列相等,那么我们就不计数([l,r]),这样我们只要数出有多少对([l,r])满足(s[l]\neq s[r]),最后答案(+1)即可

typedef long long ll;
const int N=2e5+5;
char s[N];int cnt[26],n,sum;ll res;
int main(){
	scanf("%s",s+1),n=strlen(s+1);
	fp(i,1,n)res+=sum-cnt[s[i]-'a'],++cnt[s[i]-'a'],++sum;
	printf("%lld
",res+1);
	return 0;
}

(C)

直接把那个矩阵中的点拿出来,然后按纵坐标从小到大排序,以(x)做一个最长上升子序列,如果长度(k < min(x_2 - x_1,y_2 - y_1) + 1),那么可以拐角(k)次,减少((20-5pi)k)

否则只能拐角(k-1)次然后再走一个半圆,减少((20-5pi)(k-1))再加上((10pi -20))

//quming
#include<bits/stdc++.h>
#define R register
#define fp(i,a,b) for(R int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(R int i=(a),I=(b)-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
const int N=2e5+5;const double Pi=acos(-1.0);
int x[N],y[N],id[N],f[N],num[N];
int n,tot,cnt;
inline bool cmp(const int &a,const int &b){return y[a]<y[b];}
int main(){
	int x1,y1,x2,y2;
	scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&n);
	fp(i,1,n)scanf("%d%d",&x[i],&y[i]);
	if(x1>x2)swap(x1,x2),swap(y1,y2);
	if(y1>y2){
		swap(y1,y2);
		fp(i,1,n)y[i]=y1+y2-y[i];
	}
	fp(i,1,n)id[i]=i;
	sort(id+1,id+1+n,cmp);
	fp(i,1,n){
		R int u=id[i];
		if(y[u]>=y1&&y[u]<=y2&&x[u]>=x1&&x[u]<=x2)
			num[++tot]=x[u];
	}
	cnt=0,f[cnt]=-1;
	fp(i,1,tot){
		if(num[i]>f[cnt])f[++cnt]=num[i];
		else{
			R int k=lower_bound(f+1,f+1+cnt,num[i])-f;
			f[k]=num[i];
		}
	}
	R double res;
	if(cnt<min(x2-x1,y2-y1)+1)
		res=100.0*(x2-x1+y2-y1)-(20-Pi*5)*cnt;
	else res=100.0*(x2-x1+y2-y1)-(20-Pi*5)*(cnt-1)+(10*Pi-20);
	printf("%.12lf
",res);
	return 0;
}

(D)

首先枚举(A)中的那一个位置最终和(B_1)匹配,然后对于每一个位置如果和最终的位置不等,那么判一下在过程中是否会碰到(1),如果是的话可以在那里变成

否则我们对于每一个无法达到的点求出额外向左(/)右移动的最小距离,如果令一个左边最远的走左边,那么剩下的也都可以走左边,否则这个左边最远的必须走右边。同理次远也是一样

总的复杂度(O(n^2log n))

//quming
#include<bits/stdc++.h>
#define R register
#define fi first
#define se second
#define pb emplace_back
#define fp(i,a,b) for(R int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(R int i=(a),I=(b)-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
typedef pair<int,int> pi;
const int N=2005,inf=0x3f3f3f3f;
char s[N*3],s1[N];
int sum[N*3],nl[N*3],nr[N*3],res,len;
vector<pi>vc;
void init(){
	nl[0]=-inf;
	fp(i,1,len*3)nl[i]=(s[i]=='1'?i:nl[i-1]);
	nr[len*3+1]=inf;
	fd(i,len*3,1)nr[i]=(s[i]=='1'?i:nr[i+1]);
	fp(i,1,len*3)sum[i]=sum[i-1]+(s[i]=='1');
}
inline int findl(R int x){return min(inf,x-nl[x]);}
inline int findr(R int x){return min(inf,nr[x]-x);}
int main(){
	scanf("%s%s",s1+1,s+1),res=inf;
	len=strlen(s+1);
	fp(i,len+1,len*3)s[i]=s[i-len];
	init();
	fp(i,1,len*2+1){
		vc.clear();
		R int c=0;
		fp(j,1,len)if(s1[j]!=s[i+j-1]){
			++c;
			R int r=j+len,l=j+i-1;
			if(l>r)swap(l,r);
			if(sum[r]-sum[l-1]==0)
			vc.pb(pi(findl(l),findr(r)));
		}
		vc.pb(pi(0,0));
		sort(vc.begin(),vc.end());
		reverse(vc.begin(),vc.end());
		R int tmp=inf,mx=0;
		for(auto p:vc){
			if(p.fi!=inf&&mx!=inf)cmin(tmp,mx*2+p.fi*2);
			cmax(mx,p.se);
			if(mx==inf)break;
		}
		if(tmp!=inf)cmin(res,tmp+abs(len+1-i)+c);
	}
	if(res==inf)res=-1;
	printf("%d
",res);
	return 0;
}

(E)

想到这些方法的都是什么神仙啊……

首先有用的位置只有两个,两个全(1)或者一个(1)一个(0),方便起见把全(1)记做中间点,一个(1)一个(0)记做左点,一个(0)一个(1)记做右点。记中间点个数为(A),左点和右点的个数都是(B)

我们先匹配再考虑顺序,对于一个((a_i,b_i))的匹配,把(a_i)(b_i)连一条边,那么最终的图肯定是若干个环加若干条链,环上都是中间点,链上开头和结尾分别是左右点,中间都是中间点。对于环,上面操作顺序任意,对于链,操作顺序必须固定

sol1

(f[i][j])表示把(j)个中间点放到(i)条链中的方案数,则(f[i][j]=sum _{kgeq 0}{f[i-1][j-k]over (k+1)!}),分母表示链上(k+1)条边的顺序固定,那么最终的答案就是(A!B!(A+B)!sum_{i=0}^A f[B][i]),其中((A+B)!)表示边随便排列,而(A!)(B!)代表两种点之间有标号

然后我们每一次转移相等于乘上一个多项式(sum_{igeq 0}{1over (i+1)!}x^i),那么多项式快速幂就行了,复杂度(O(nlog n))

sol2

另一种方法是设(f[i][j])表示一共放了(i)个中间点和(j)个左点,则

[egin{aligned} f[i][j]=i imes j imes f[i-1][j]+j^2 imes f[i][j-1] end{aligned} ]

前一个是表示插入一个中间点,它的标号任意,即可以随便和之前一个交换标号,所以乘(i),然后可以插在任意一条链的后面,所以乘(j)

后一个是表示插入一个新的左点,同理标号任意所以乘(j),又因为可以选择任意一个右点所以再乘(j)

复杂度(O(n^2))已经可以通过了

不过还可以用多项式优化,设(g[i][j]={f[i][j]over (j!)^2(i!)}),则有(g[i][j]=g[i][j-1]+j imes g[i-1][j])

原来的可以看成是一个网格从左上出发到右下,每次往下走就乘上(ij),往右走就乘上(j^2),求所有路径权值和,变成(g[i][j])之后就是只有往下走会乘上(j)

那么我们枚举在第(i)列走了几步,发现第(i)列的生成函数就是({1over 1-ix}),答案就是所有列的生成函数之积,直接分治(NTT+)多项式求逆即可,复杂度(O(nlog^2 n))

因为比较懒所以只写了(O(n^2))

//quming
#include<bits/stdc++.h>
#define R register
#define fp(i,a,b) for(R int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(R int i=(a),I=(b)-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
const int P=998244353;
inline void upd(R int &x,R int y){(x+=y)>=P?x-=P:0;}
inline int add(R int x,R int y){return x+y>=P?x+y-P:x+y;}
inline int dec(R int x,R int y){return x-y<0?x-y+P:x-y;}
inline int mul(R int x,R int y){return 1ll*x*y-1ll*x*y/P*P;}
int ksm(R int x,R int y){
	R int res=1;
	for(;y;y>>=1,x=mul(x,x))(y&1)?res=mul(res,x):0;
	return res;
}
const int N=10005;
char s[N],t[N];int fac[N],ifac[N],f[N][N],n,cnta,cntb;
inline int C(R int n,R int m){return m>n?0:1ll*fac[n]*ifac[m]%P*ifac[n-m]%P;}
inline void init(int n=10000){
	fac[0]=ifac[0]=1;fp(i,1,n)fac[i]=mul(fac[i-1],i);
	ifac[n]=ksm(fac[n],P-2);fd(i,n-1,1)ifac[i]=mul(ifac[i+1],i+1);
}
int main(){
	init();
	scanf("%s%s",s+1,t+1),n=strlen(s+1);
	fp(i,1,n){
		cnta+=(s[i]=='1'&&t[i]=='1');
		cntb+=(s[i]=='1'&&t[i]=='0');
	}
	f[0][0]=1;
	fp(j,1,cntb)f[0][j]=mul(fac[j],fac[j]);
	fp(i,1,cnta)fp(j,1,cntb)
		f[i][j]=(1ll*i*j%P*f[i-1][j]+1ll*j*j%P*f[i][j-1])%P;
	R int res=0;
	fp(i,0,cnta){
		R int j=cnta-i;
		upd(res,1ll*f[i][cntb]*fac[j]%P*fac[j]%P*C(cnta,j)%P*C(cnta+cntb,j)%P);
	}
	printf("%d
",res);
	return 0;
}

(F)

好秒……

首先假设现在还剩(n)(Yes)(m)(No),那么肯定是选择较多的那个回答,如果一样的话随便蒙一个

然后我们把这个东西放到一个坐标系里,那么一种回答的方案相当于是从((n,m))出发到((0,0)),如果以(y=x)把坐标系分成两部分,则在这条线下面往左走会有贡献,在上面往下走会有贡献

先假设(n=m),且走的过程中没有碰到过对角线,那么不管在上面走还是在下面走贡献都是(n)

否则先假设(ngeq m),那么在走到对角线之前,一定会先往左走(n-m)步,如果到了对角线,我们以每次碰到对角线为界把路径分成若干部分,那么每一部分的贡献都是之前说的没有碰到过对角线的贡献了,所以总的贡献必定是(n),同理当(mgeq n)的时候贡献一定是(m)

然后是对角线上的点,不管往哪边走贡献都是({1over 2}),那么我们对于每个点判断一下经过它的概率是多少即可

//quming
#include<bits/stdc++.h>
#define R register
#define fp(i,a,b) for(R int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(R int i=(a),I=(b)-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
const int P=998244353;
inline void upd(R int &x,R int y){(x+=y)>=P?x-=P:0;}
inline int add(R int x,R int y){return x+y>=P?x+y-P:x+y;}
inline int dec(R int x,R int y){return x-y<0?x-y+P:x-y;}
inline int mul(R int x,R int y){return 1ll*x*y-1ll*x*y/P*P;}
int ksm(R int x,R int y){
	R int res=1;
	for(;y;y>>=1,x=mul(x,x))(y&1)?res=mul(res,x):0;
	return res;
}
const int N=1e6+5;
int fac[N],ifac[N],n,m,res;
inline int C(R int n,R int m){return m>n?0:1ll*fac[n]*ifac[m]%P*ifac[n-m]%P;}
inline int calc(R int n,R int m){return C(n+m,n);}
inline void init(int n=1e6){
	fac[0]=ifac[0]=1;fp(i,1,n)fac[i]=mul(fac[i-1],i);
	ifac[n]=ksm(fac[n],P-2);fd(i,n-1,1)ifac[i]=mul(ifac[i+1],i+1);
}
int main(){
	init();
	scanf("%d%d",&n,&m);
	fp(i,1,min(n,m))upd(res,mul(calc(i,i),calc(n-i,m-i)));
	res=mul(res,ksm(mul(calc(n,m),2),P-2));
	upd(res,max(n,m));
	printf("%d
",res);
	return 0;
}
原文地址:https://www.cnblogs.com/yuanquming/p/11600267.html