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\n",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\n",res+1);
return 0;
}
\(C\)
直接把那个矩阵中的点拿出来,然后按纵坐标从小到大排序,以\(x\)做一个最长上升子序列,如果长度\(k < min(x_2 - x_1,y_2 - y_1) + 1\),那么可以拐角\(k\)次,减少\((20-5\pi)k\)
否则只能拐角\(k-1\)次然后再走一个半圆,减少\((20-5\pi)(k-1)\)再加上\((10\pi -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\n",res);
return 0;
}
\(D\)
首先枚举\(A\)中的那一个位置最终和\(B_1\)匹配,然后对于每一个位置如果和最终的位置不等,那么判一下在过程中是否会碰到\(1\),如果是的话可以在那里变成
否则我们对于每一个无法达到的点求出额外向左\(/\)右移动的最小距离,如果令一个左边最远的走左边,那么剩下的也都可以走左边,否则这个左边最远的必须走右边。同理次远也是一样
总的复杂度\(O(n^2\log 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\n",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 _{k\geq 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_{i\geq 0}{1\over (i+1)!}x^i\),那么多项式快速幂就行了,复杂度\(O(n\log n)\)
sol2
另一种方法是设\(f[i][j]\)表示一共放了\(i\)个中间点和\(j\)个左点,则
前一个是表示插入一个中间点,它的标号任意,即可以随便和之前一个交换标号,所以乘\(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\times g[i-1][j]\)
原来的可以看成是一个网格从左上出发到右下,每次往下走就乘上\(ij\),往右走就乘上\(j^2\),求所有路径权值和,变成\(g[i][j]\)之后就是只有往下走会乘上\(j\)
那么我们枚举在第\(i\)列走了几步,发现第\(i\)列的生成函数就是\({1\over 1-ix}\),答案就是所有列的生成函数之积,直接分治\(NTT+\)多项式求逆即可,复杂度\(O(n\log^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\n",res);
return 0;
}
\(F\)
好秒……
首先假设现在还剩\(n\)个\(Yes\)和\(m\)个\(No\),那么肯定是选择较多的那个回答,如果一样的话随便蒙一个
然后我们把这个东西放到一个坐标系里,那么一种回答的方案相当于是从\((n,m)\)出发到\((0,0)\),如果以\(y=x\)把坐标系分成两部分,则在这条线下面往左走会有贡献,在上面往下走会有贡献
先假设\(n=m\),且走的过程中没有碰到过对角线,那么不管在上面走还是在下面走贡献都是\(n\)
否则先假设\(n\geq m\),那么在走到对角线之前,一定会先往左走\(n-m\)步,如果到了对角线,我们以每次碰到对角线为界把路径分成若干部分,那么每一部分的贡献都是之前说的没有碰到过对角线的贡献了,所以总的贡献必定是\(n\),同理当\(m\geq n\)的时候贡献一定是\(m\)
然后是对角线上的点,不管往哪边走贡献都是\({1\over 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\n",res);
return 0;
}