1. 程式人生 > 實用技巧 >某古 11 月月賽 I 遊記

某古 11 月月賽 I 遊記

某古 11 月月賽 I 遊記

難度好評,沒有像我上次打的那場比賽那麼水了,不過自己的分數還是好低,只會前三題。。。

希望你古月賽的題目一直都能像這場這麼有意思。

A 「MCOI-03」正方

題目分析

三角形面積公式 \(S=\frac{1}{2}ah\) ,由於 \(a\) 相等,所以題目給出的其實就是 \(h\) 之比。

題目中給出了 \(a,b,c,d\) ,不妨令 \(a\le b\le c\le d\) ,由於題目給出的是正方形,所以必須要滿足 \(a+d=b+c\) ,否則不是正方形,答案就是 \(0\) ,如果滿足 \(a+d=b+c\) ,不妨令正方形的左下角為 \((0,0)\) ,正方形邊長為 \(a+d\)

,那麼這個點的選擇方法就只有 \((a,b),(a,c),(b,a),(c,a),(d,b),(d,c),(b,d),(c,d)\) 八種,排序後去重就行了。

參考程式碼

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
	int f;char c;
	for(f=1,c=ch();c<'0'||c>'9';c=ch())if(c=='-')f=-f;
	for(x=0;c<='9'&&c>='0';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
	static char q[64];int cnt=0;
	if(!x)pc('0');if(x<0)pc('-'),x=-x;
	while(x)q[cnt++]=x%10+'0',x/=10;
	while(cnt--)pc(q[cnt]);
}
long long A[4];
pair<long long,long long>s[8];
int main(){
	int q;read(q);
	while(q--){
		for(int i=0;i<4;++i)read(A[i]);sort(A,A+4);
		if(A[0]+A[3]!=A[1]+A[2])puts("0");
		else{
			int cn=0;
			for(int i=0;i<=3;i+=3){
				for(int j=1;j<=2;j+=1){
					s[cn++]=pair<long long,long long>(A[i],A[j]);
					s[cn++]=pair<long long,long long>(A[j],A[i]);
				}
			}
			sort(s,s+cn);cn=unique(s,s+cn)-s;write(cn),pc('\n');
		}
	} 
	return 0;
}

B 「MCOI-03」村國

題目分析

一開始看到這題真的沒有頭緒,但是仔細思考後發現題面就是唬人的,感覺這樣的題目還是挺有意思的。

假如當前好感值最高的點是 \(x\) ,那麼會先選擇一直去 \(x\) 直到 \(x\) 旁邊出現了一個點的好感值和 \(x\) 相等並且編號儘可能小,不妨設這個點為 \(y\) ,那麼就會一直先去 \(x,y\) 中編號小的,然後去 \(x,y\) 中編號大的一直迴圈,所以此時只需要判斷 \(m\) 的奇偶性就行了。

需要注意的一點就是如果 \(x\) 不存在相鄰的點答案就是 \(x\)

參考程式碼

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
	int f;char c;
	for(f=1,c=ch();c<'0'||c>'9';c=ch())if(c=='-')f=-f;
	for(x=0;c<='9'&&c>='0';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
	static char q[64];int cnt=0;
	if(!x)pc('0');if(x<0)pc('-'),x=-x;
	while(x)q[cnt++]=x%10+'0',x/=10;
	while(cnt--)pc(q[cnt]);
}
const int maxn=2000006;
int a[maxn];
int main(){
	int T;read(T);
	while(T--){
		int n;long long m;int po=0;read(n),read(m);
		for(int i=1;i<=n;++i){
			read(a[i]);if(a[i]>a[po])po=i;
		}
		int bo=0;
		for(int i=1;i<n;++i){
			int u,v;read(u),read(v);if(v==po)u^=v^=u^=v;
			if(u==po&&(a[v]>a[bo]||(a[v]==a[bo]&&v<bo)))bo=v;
		}
		if(!bo)write(po);
		else{
			int delta=a[po]-a[bo];
			if(m<delta)write(po);
			else{
				m-=delta;
				if(po>bo)po^=bo^=po^=bo;
				if(m&1)write(bo);else write(po);
			}
		}
		pc('\n');
	}
	return 0;
}

C 「MCOI-03」括號

題目分析

\(k=0\) 的時候,可以通過一個棧來求答案,不斷刪去匹配括號最後留下的串肯定是 )))...))((...((( ,答案就是這個串的長度,下面只考慮 \(k>0\) 的情況。

考慮 \(S(l,r)\) 的 0 級偏值給答案造成貢獻時需要乘以的係數,要從串 \(S(1,n)\) 得到 \(S(l,r)\) 左端點需要向右邊移動 \(l-1\) 次,右端點需要向左邊移動 \(n-r\) 次,這些移動都是在 \(k\) 步裡面完成的,由隔板法就可以得出要乘上的係數就是 \({l-1+k-1\choose k-1}\times {n-r+k-1\choose k-1}\) ,不妨設 \(S(l,r)\) 的 0 級偏值等於 \(T(l,r)\) ,那麼答案就是 \(\sum_{l=1}^n\sum_{r=l}^n(T(l,r)\times {l-1+k-1\choose k-1}\times {n-r+k-1\choose k-1})\)

如何統計所有的 \(S(l,r)\) ?考慮從右邊往左邊掃,由 \(\sum_{r=l+1}^n(T(l+1,r)\times {n-r+k-1\choose k-1})\) 遞推得出 \(\sum_{r=l}^n(T(l,r)\times {n-r+k-1\choose k-1})\)

首先需要知道,如果固定左端點,隨著右端點的增加,不斷刪去匹配括號得到的串 )))...))((...((( 中的左半部分(即 )))...)) )的長度一定會不斷增加,那麼我們刪去匹配括號最終保留的串肯定是這樣的:

)))(...((
)))(...((
)))(...((
))(...((
))(...((
))(...((
)(...((
(...((
(...((
.
.
.
(

假如左邊添加了一個 )

))))(...((
))))(...((
))))(...((
)))(...((
)))(...((
)))(...((
))(...((
)(...((
)(...((
.
.
.
)(
)

只是每個串的長度增加了 \(1\) ,所以此時滿足:

\[\sum_{r=l}^n(T(l,r)\times {n-r+k-1\choose k-1})=\sum_{r=l}^n{n-r+k-1\choose k-1}+\sum_{r=l+1}^n(T(l+1,r)\times {n-r+k-1\choose k-1}) \]

假如左邊添加了一個 (

()))(...((
()))(...((
()))(...((
())(...((
())(...((
())(...((
()(...((
((...((
((...((
.
.
.
((
(

此時可能出現匹配括號,需要把匹配括號刪去:

  )(...((
  )(...((
  )(...((
  (...((
  (...((
  (...((
  (...((
((...((
((...((
.
.
.
((
(

不難發現,所有原本存在 ) 的子串的長度都減小了 \(2\) ,不妨設最小的滿足 \(S(l+1,r)\) 左邊保留了一個 )\(r\)\(p\) ,那麼可以得到:

\[\sum_{r=l}^n(T(l,r)\times {n-r+k-1\choose k-1})=\sum_{r=l}^n{n-r+k-1\choose k-1}-2\sum_{r=p}^n{n-r+k-1\choose k-1}+\sum_{r=l+1}^n(T(l+1,r)\times {n-r+k-1\choose k-1}) \]

由此我們可以得出,我們需要維護的就是 ) 數量不同的所有斷點,不難發現這個是很好維護的,用一個棧維護就行了,時間複雜度 \(\mathcal O(n+k)\)

參考程式碼

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
	int f;char c;
	for(f=1,c=ch();c<'0'||c>'9';c=ch())if(c=='-')f=-f;
	for(x=0;c<='9'&&c>='0';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
	static char q[64];int cnt=0;
	if(!x)pc('0');if(x<0)pc('-'),x=-x;
	while(x)q[cnt++]=x%10+'0',x/=10;
	while(cnt--)pc(q[cnt]);
}
const int mod=998244353,maxn=2000005;
int mo(const int x){
	return x>=mod?x-mod:x;
}
int fac[maxn],iac[maxn];
int binom(int n,int m){
	return 1ll*iac[m]*iac[n-m]%mod*fac[n]%mod;
}
char s[maxn];
int st[maxn],tp;
int main(){
	int n,k;read(n),read(k);
	if(k==0){
		scanf("%s",s+1);
		int now=0,ans=0;
		for(int i=1;i<=n;++i){
			if(s[i]==')'){
				if(now)--now;
				else ++ans;
			}
			else ++now;
		}
		ans+=now;
		write(ans),pc('\n');
	}
	else{
		int mx=n+k;
		iac[0]=iac[1]=fac[0]=fac[1]=1;
		for(int i=2;i<=mx;++i)
			iac[i]=1ll*(mod-mod/i)*iac[mod%i]%mod;
		for(int i=2;i<=mx;++i)
			fac[i]=1ll*fac[i-1]*i%mod,iac[i]=1ll*iac[i-1]*iac[i]%mod;
		scanf("%s",s+1);
		int ans=0,sum=0,all=0;
		for(int i=n;i>=1;--i){
			sum=mo(sum+binom(n-i+k-1,k-1));
			all=mo(all+sum);
			if(s[i]=='('){
				if(tp){
					all=mo(mo(mo(mod-st[tp])<<1)+all);--tp;
				}
			}
			else{
				++tp;st[tp]=sum;
			}
			ans=mo(ans+1ll*all*binom(i-1+k-1,k-1)%mod);
		}
		write(ans),pc('\n');
	}
	return 0;
}

總結

這次月賽沒有打多久,這個成績勉強滿意吧。