AtCoder Grand Contest 019

费劲千辛万苦,终于自己独立做出了一场 A G C AGC AGC,尽管蒟蒻总共花费了近 5 h 5h 5h,不过依旧很开心,今天晚上可以奖励自己吃点宵夜。

A - Ice Tea Store

按照 2 L 2L 2L的容量排个序,如果最小的是 2 L 2L 2L装的,那么就先拿 2 L 2L 2L装,再拿第二小的装剩下的,否则直接拿最小的装。

#include<bits/stdc++.h>
using namespace std;

pair<int,int> e[4];
int a,b,c,d,n;

int main(){
	scanf("%d %d %d %d",&a,&b,&c,&d);
	scanf("%d",&n);
	e[0].first=a*8;e[0].second=0;
	e[1].first=b*4;e[1].second=1;
	e[2].first=c*2;e[2].second=2;
	e[3].first=d;e[3].second=3;
	sort(e,e+4);
	if(e[0].second<=2) printf("%lld\n",1ll*n*e[0].first/2);
	else printf("%lld\n",1ll*(n/2)*e[0].first+(n%2)*e[1].first/2);
}

B - Reverse and Compare

与其他不同当且仅当区间端点的两个字符不同。
若端点的两个字符相同,那么与交换 [ l + 1 , r − 1 ] [l+1,r-1] [l+1,r1]相同。
端点不同显然与其他不同。

#include<bits/stdc++.h>
using namespace std;

const int N=200010;
char s[N];
int n,t[26];

int main(){
	scanf("%s",s+1);n=strlen(s+1);
	long long ans=0;
	for(int i=1;i<=n;i++) ans+=i-1-t[s[i]-'a'],t[s[i]-'a']++;
	printf("%lld\n",ans+1);
}

C - Fountain Walk

很容易想到最长上升子序列,事实上就是这样。
手玩很久才发现,最后如果最大长度等于宽度或者长度,答案要加一个半圆。
充要性显然。

#include<bits/stdc++.h>
#define pii pair<int,int>
#define fi first
#define se second
#define mk make_pair
using namespace std;

const int N=200010;
int a,b,c,d,n,m,f[N];
const double Pi=acos(-1.0);
pii p[N];

int main(){
	scanf("%d %d %d %d",&a,&b,&c,&d);
	if(c<a){swap(a,c);swap(b,d);}
	scanf("%d",&n);
	int x,y;
	for(int i=1;i<=n;i++){
		scanf("%d %d",&x,&y);
		if(a<=x && x<=c && min(b,d)<=y && y<=max(b,d))
			p[++m]=mk(x,y);
	}
	sort(p+1,p+1+m);
	if(a==c || b==d){
		printf("%.15lf\n",100.0*(abs(c-a)+abs(b-d))+(m?10*Pi-20:0));
		return 0;
	}
	if(b>d) reverse(p+1,p+1+m);
	int mmax=0;f[0]=-1;
	for(int i=1;i<=m;i++){
		int l=0,r=mmax,ans=0;
		while(l<=r){
			int mid=(l+r)/2;
			if(p[i].second>f[mid]) l=(ans=mid)+1;
			else r=mid-1;
		}
		ans++;
		if(ans==mmax+1) f[ans]=p[i].second,mmax++;
		else f[ans]=min(f[ans],p[i].second);
	}
	printf("%.15lf\n",100.0*(abs(c-a)+abs(b-d))+mmax*(Pi*5-20)+(mmax==c-a+1 || mmax==abs(b-d)+1)*(Pi*5));
}

D - Shift and Flip

枚举最后一步是什么,再枚举最后实际上转了多少位。
如果是向左转,一些点可能向右转一些,反转之后再向左转回来。
可以给他们按向右转的次数排个序,然后枚举一下就可以了。

#include<bits/stdc++.h>
#define pii pair<int,int>
#define fi first
#define se second
#define mk make_pair
using namespace std;

const int N=2010;
char a[N],b[N];
int n,l[N],r[N],pre[N];
pii p[N];

int main(){
	scanf("%s",a+1);n=strlen(a+1);
	scanf("%s",b+1);
	int las=1e9;
	for(int i=1;i<=n;i++){
		las++;
		if(b[i]=='1') las=0;
		l[i]=las;
	}
	if(las>=1e9){
		int ans=1e9;
		for(int j=0;j<n;j++){
			bool tf=true;
			for(int i=1;i<=n;i++) if(a[i]!=b[(i+j-1)%n+1]){
				tf=false;break;
			}
			if(tf) ans=min(ans,min(j,n-j));
		}
		if(ans==1e9) printf("-1\n");
		else printf("%d\n",ans);
		return 0;
	}
	for(int i=1;i<=n;i++){
		las++;
		if(b[i]=='1') las=0;
		l[i]=las;
	}las=1e9;
	for(int i=n;i>=1;i--){
		las++;
		if(b[i]=='1') las=0;
		r[i]=las;
	}
	for(int i=n;i>=1;i--){
		las++;
		if(b[i]=='1') las=0;
		r[i]=las;
	}
	int ans=1e9;
	for(int i=0;i<=n;i++){
		int tot=0,m=0;
		for(int j=1;j<=n;j++){
			int nex=(j+i-1)%n+1;
			if(a[j]!=b[nex]){
				if(a[j]=='1') p[++m]=mk(r[j],l[j]);
				tot++;
			}
		}
		sort(p+1,p+1+m);pre[m+1]=0;
		for(int j=m;j>=1;j--) pre[j]=max(pre[j+1],p[j].second);
		for(int j=0;j<=m;j++) ans=min(ans,tot+pre[j+1]*2+max(i,p[j].first)*2-i);
	}
	for(int i=0;i<=n;i++){
		int tot=0,m=0;
		for(int j=1;j<=n;j++){
			int nex=(j+n-i-1)%n+1;
			if(a[j]!=b[nex]){
				if(a[j]=='1') p[++m]=mk(l[j],r[j]);
				tot++;
			}
		}
		sort(p+1,p+1+m);pre[m+1]=0;
		for(int j=m;j>=1;j--) pre[j]=max(pre[j+1],p[j].second);
		for(int j=0;j<=m;j++) ans=min(ans,tot+pre[j+1]*2+max(i,p[j].first)*2-i);
	}
	printf("%d\n",ans);
}

E - Shuffle and Swap

比较蠢,想了1.5h
实际上考虑将 n n n个点按照 ( A i , B i ) (A_i,B_i) (Ai,Bi)划分成四类 ( 0 , 1 ) , ( 1 , 0 ) , ( 1 , 1 ) , ( 0 , 0 ) (0,1),(1,0),(1,1),(0,0) (0,1),(1,0),(1,1),(0,0),分别设置为 X , Y , Z X,Y,Z X,Y,Z类,最后一类直接扔掉。
两两交换肯定形成了一个置换环。将置换环写成链的形状,容易发现,一定是 Y Z . . . Z X YZ...ZX YZ...ZX。中间的 Z Z Z有多少个无所谓。
那么最后的序列实际上就是许多这样的链和一堆 Z Z Z胡乱交换,我们先考虑比较困难的前面这一部分。
下面用 ( X , Y ) (X,Y) (X,Y)来表示将 X , Y X,Y X,Y交换,序列指的是最后的交换序列, P P P Y Y Y Z Z Z
考虑 D p Dp Dp来计算这样的方案数就好了。
f [ i ] [ j ] f[i][j] f[i][j]表示当前放了 i i i Z Z Z j j j条链的方案数。
f [ i ] [ j ] = f [ i − 1 ] [ j ] ∗ j + f [ i ] [ j − 1 ] f[i][j]=f[i-1][j]*j+f[i][j-1] f[i][j]=f[i1][j]j+f[i][j1]
f [ i − 1 ] [ j ] ∗ j f[i-1][j]*j f[i1][j]j表示在序列前面的位置中将某个 ( P , X ) (P,X) (P,X),替换成 ( P , Z ) (P,Z) (P,Z),并在当前这一位放上 ( Z , X ) (Z,X) (Z,X)。我们按照第一次出现的顺序给每一个 Z Z Z编号,所以就有了顺序,乘上一个系数。
f [ i ] [ j − 1 ] f[i][j-1] f[i][j1]表示直接在当前位置上放一个 ( Y , X ) (Y,X) (Y,X)
后面直接枚举剩下多少个 Z Z Z就可以了,他们可以随便交换,乘上一些组合数把他们插到序列里去就好。
发现其实 f f f很眼熟,没错,他就是 S ( i + j , j ) S(i+j,j) S(i+j,j)

#include<bits/stdc++.h>
using namespace std;

const int N=10010,mod=998244353;
char a[N],b[N];
int fac[20010],inv[20010],f[10001][10001];
int n;

int ksm(int x,int t){
	int tot=1;
	while(t){
		if(t&1) tot=1ll*tot*x%mod;
		x=1ll*x*x%mod;
		t/=2;
	}
	return tot;
}

void ad(int&x,int y){x=(x+y>=mod)?(x+y-mod):(x+y);}
int C(int x,int y){return 1ll*fac[x]*inv[y]%mod*inv[x-y]%mod;}

int main(){
	scanf("%s",a+1);n=strlen(a+1);
	scanf("%s",b+1);
	int ta=0,tb=0;
	for(int i=1;i<=n;i++) if(a[i]=='1'){
		if(b[i]=='1') ta++;
		else tb++;
	}
	fac[0]=1;for(int i=1;i<=20000;i++) fac[i]=1ll*fac[i-1]*i%mod;
	inv[20000]=ksm(fac[20000],mod-2);for(int i=19999;i>=0;i--) inv[i]=1ll*inv[i+1]*(i+1)%mod;
	f[0][0]=1;
	for(int i=0;i<=ta;i++)
		for(int j=0;j<=tb;j++){
			if(i) f[i][j]=1ll*f[i-1][j]*j%mod;
			if(j) ad(f[i][j],f[i][j-1]);
		}
	int ans=0;
	for(int i=0;i<=ta;i++)
		ans=(ans+1ll*f[i][tb]*fac[i]%mod*C(ta+tb,ta-i)%mod*C(ta,i)%mod*fac[ta-i]%mod*fac[ta-i])%mod;
	ans=1ll*ans*fac[tb]%mod*fac[tb]%mod;
	printf("%d\n",ans);
}

F - Yes or No

感觉比 E E E题简单一些,想了 1 h 1h 1h
显然谁剩的多猜谁,将剩下的差值设为 A A A
考虑一条折线, ( i , A i ) (i,A_i) (i,Ai)表示第 i i i次的 A A A A i A_i Ai
最后肯定是 ( n + m , 0 ) (n+m,0) (n+m,0)
A A A不为 0 0 0,往 y = 0 y=0 y=0靠近的时候 a n s + + ans++ ans++,远离的时候不变,这一部分的贡献是 max ( n , m ) \text{max}(n,m) max(n,m)
若当前 A = 0 A=0 A=0,那么下一步盲猜,有 1 / 2 1/2 1/2的几率猜中,这一部分的贡献可以将经过 y = 0 y=0 y=0的次数期望算出来,乘上 1 / 2 1/2 1/2
对于每一个点单独计算概率即可,简单的组合数学。

#include<bits/stdc++.h>
using namespace std;

const int mod=998244353,N=1000010;
int n,m,fac[N],inv[N];

int ksm(int x,int t){
	int tot=1;
	while(t){
		if(t&1) tot=1ll*tot*x%mod;
		x=1ll*x*x%mod;
		t/=2;
	}
	return tot;
}

int C(int x,int y){return 1ll*fac[x]*inv[y]%mod*inv[x-y]%mod;}

int main(){
	scanf("%d %d",&n,&m);
	if(m<n) swap(n,m);
	fac[0]=1;for(int i=1;i<N;i++) fac[i]=1ll*fac[i-1]*i%mod;
	inv[N-1]=ksm(fac[N-1],mod-2);for(int i=N-2;i>=0;i--) inv[i]=1ll*inv[i+1]*(i+1)%mod;
	int ans=0;
	for(int i=0;i<n+m;i++) if((i&1)==((m-n)&1) && m-n<=i)
		ans=(ans+1ll*C(i,(i-(m-n))/2)*C(n+m-i,n-(i-(m-n))/2))%mod;
	ans=1ll*(mod+1)/2*ans%mod*ksm(C(n+m,n),mod-2)%mod;
	printf("%d\n",(ans+m))%mod;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值