Edu Round 170 Review

Hanggoash發表於2024-10-18

Edu Round 170 Review

A

分析

一個很顯然的根據字首劃分的貪心,直接指標模擬就好了。

Code

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int t;
	cin>>t;
	while(t--)
	{
		string a,b;
		cin>>a>>b;
		int l1=a.length(),l2=b.length();
		int p=0;
		while(p<l1&&p<l2&&a[p]==b[p])p++;
		if(p>0)p--;
		cout<<l1+l2-p<<'\n';
	}

}

B

分析

打表就發現需要輸出的就是 \(2^n\) ,然而這道題我卡了十多分鐘,實在是有點sb了。

具體證明的話可以用數學歸納法,也可以直接證明,都是比較容易的。

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MOD=1e9+7;
inline int power(int a,int b)
{
	int ans=1;
	while(b)
	{
		if(b&1)ans=ans*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return ans%MOD; 
}
const int N=2e5+1;
int t,n[N],k[N];
inline void solve()
{
	cin>>t;
	for(int i=1;i<=t;++i)scanf("%d",&n[i]);
	for(int i=1;i<=t;++i)scanf("%d",&k[i]),printf("%lld\n",power(2,k[i]));
}
signed main()
{
	solve();
}

C

分析

其實思路上面很簡單,只需要以每個數為開頭貪心地連續去選擇儘可能多的數字就可以了。

注意這樣做是 \(O(n^2)\) 的,那麼考慮如何在列舉的時候快速地從現在的答案推知下一個答案。

很容易想到一個簡化的莫隊,從 \([l,r]\)\([l+1,r+1]\) ,實際上只是增加了一個數,去除了一個數而已,透過增量就可以計算出來。

邊界寫錯了吃了兩發罰時(實際上可以不需要去重的,所以說本人的找屎能力真的強)。

又成功地在模擬題上面寫出了一道屎山程式碼。。。

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
template<typename T>inline void re(T &x)
{
	x=0;int f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	x*=f;
}
template<typename T>inline void wr(T x)
{
	if(x>9)wr(x/10);
	putchar(x%10^48);
}
int t,n,k,a[200010];
pair<int,int>b[200010];
inline void solve()
{
	re(n),re(k);
	for(register int i=1;i<=n;++i)re(a[i]);
	sort(a+1,a+n+1);a[n+1]=0;
	int cnt=1,p=0;
	for(register int i=1;i<=n;++i)
	{
		if(a[i]!=a[i+1])
		{
			b[++p].first=a[i];
			b[p].second=cnt;
			cnt=1;
		}
		else cnt++;
	}
	int ans=-1,lasl=0,lasr=0;
	int lasans=0;
	for(register int i=1;i<=p;++i)
	{
		if(!lasans)
		{
			int p1=i+1;lasans=b[i].second;
			while(p1<=p&&p1-i+1<=k&&b[p1].first-b[p1-1].first==1)lasans+=b[p1++].second;
			ans=max(ans,lasans),lasl=i,lasr=p1-1;
		}
		else
		{
			int nowr=lasr+i-lasl;
			if(b[nowr].first-b[nowr-1].first!=1||nowr>p)
			{
				i=nowr-1;
				lasans=lasl=lasr=0;
				continue;
			}
			lasans-=b[i-1].second,lasans+=b[nowr].second;
			ans=max(ans,lasans);
		}
	}
	wr(ans),putchar('\n');
}
signed main()
{
	re(t);
	while(t--)
		solve();
	return 0;
}	

D

分析

因為實現能力太差,C做了一個小時,導致D只有不到二十分鐘的時間了,大概掃過去想了一個很騷的做法,就是倒敘列舉然後貪心地選擇貢獻即可,結果發現題看錯了。。。

這種涉及到抉擇的題發現貪心是假的,那肯定就是DP了。

那其實階段和狀態也很容易設計出來。

定義 \(dp[i][j]\) 為前 \(i\) 個數字 選擇了 \(j\) 點智力加點能夠獲得的分數(注意此時體力的加點一定是唯一確定的,因此我們沒有必要單獨開一維來單獨記錄,前提是我們需要統計出來一個陣列來記錄任意某個位置 \(pos\) 之前一共出現了多少個 \(0\) ) 。

那麼轉移方程也非常的水到渠成:

如果 \(a_i=0\) ,那麼所有的 \(dp[i][j]=\max(dp[i-1][j],dp[i-1][j-1])\)

如果 \(a_i>0\) ,那麼所有的 \(dp[i][j](j\ge a_i)=dp[i-1][j]+1\)

如果 \(a_i<0\) ,那麼所有的 \(dp[i][j](j\le sum0-(-a_i))=dp[i-1][j]+1\)

注意到我們會多次執行區間加的操作,實際上你可以寫一個線段樹,或者是樹狀陣列,然而都太麻煩或者不好實現(對我來說)。

我們可以直接採用差分陣列,但是缺陷就在於,雖然可以多次進行區間加法,但是一隻能統計一次。觀察到這道題 \(m\) 的資料範圍非常小,所以每次遇到一個 \(0\) 之前,我們就暴力地統計一遍差分陣列,然後加到 \(dp\) 陣列裡面,而後進行 \(0\) 處該進行的轉移,再把差分陣列清零,重新進行後續的區間加操作即可。

Code

#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void re(T &x)
{
	x=0;int f=1;char c=getchar();
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
	x*=f;
}
template<typename T>inline void wr(T x)
{
	if(x>9)wr(x/10);
	putchar(x%10^48);
} 
const int N=2e6+100;
int a[N],dp[5010];
int n,m;
int s[5010];
inline void add(int l,int r,int v)
{
	s[l]+=v,s[r+1]-=v;
}
inline void	calcsum()
{
	for(int i=1;i<=m;++i)
		s[i]+=s[i-1];
	for(int i=0;i<=m;++i)
		dp[i]+=s[i],s[i]=0;
}	
inline void pre()
{
	re(n),re(m);
	for(register int i=1;i<=n;++i)re(a[i]);
}
inline void solve()
{
	int sum0=0;
	for(register int i=1;i<=n;++i)
	{
		if(a[i]==0)
		{
			calcsum();
			for(register int j=++sum0;j>=1;--j)
				dp[j]=max(dp[j],dp[j-1]);	
		}
		else if(a[i]>0)
		{
			if(sum0<a[i])continue;
			add(a[i],sum0,1);
		}
		else
		{
			if(sum0<abs(a[i]))continue;
			add(0,sum0+a[i],1);					
		}
	}
	calcsum();
	int ans=-1;
	for(register int i=0;i<=m;++i)ans=max(ans,dp[i]);
	wr(ans);
}
int main()
{
	pre();
	solve();
	return 0;
}
/*
dp[i][j] 前i個數字  擁有 j 點智力可以獲得的分數

all dp[i][j]=dp[i-1][j];
if(0)
	dp[i][j]=max dp[i][j],dp[i-1][j-1];
if(+)
	dp[i][j]=add(a[i],sum0[i],1);
if(-)  
	dp[i][j]=add(0,sum0[i]+a[i],1);
	
寫的時候的問題:
	字首和之後沒有給dp 0加上去
	統計差分 和 進行 O(m) 轉移的順序搞反了  
*/

E

非常好的一道計數題,讓我“溫習“了一下在高二的時候學習過的卡特蘭數。

前置知識

卡特蘭數

定義背景

由於這一類組合數對於實際問題有意義,所以才會被單獨討論,它適用於如下情況:

  1. \(n\) 個人排成一行進入劇場。入場費 5 元。其中只有個 \(n\) 人有一張 5 元鈔票,另外人 \(n\) 只有 10 元鈔票,劇院無其它鈔票,問有多少種方法使得只要有 10 元的人買票,售票處就有 5 元的鈔票找零?
  2. 有一個大小為 \(n\times n\) 的方格圖開始每次都只能向右或者向上走一單位,不走到對角線上方(但可以觸碰)的情況下到達右上角有多少可能的路徑?
  3. 在圓上選擇 \(2n\) 個點,將這些點成對連線起來使得所得到的條 \(n\) 線段不相交的方法數?
  4. 對角線不相交的情況下,將一個凸多邊形區域分成三角形區域的方法數?
  5. 一個棧(無窮大)的進棧序列為 \(1-n\) 有多少個不同的出棧序列?
  6. \(n\) 個結點可構造多少個不同的二叉樹?
  7. \(n\)\(+-1\) 組成的序列,滿足任意字首和非負,有多少種構造方案?

公式

  1. \(H_n=\frac{C_{2n}^{n}}{n+1}\)

  2. \(H_n=C_{2n}^n-C_{2n}{n-1}\)

  3. \(H_n=H_{n-1}\times \frac{4n-2}{n+1}\)

  4. DP的方式求(卡特蘭數實際上是一個匹配的過程)

    定義 \(f_{i,j}\) 為,前 \(i\) 個數,剩下了 \(j\) 個沒有匹配的方案數,那麼最後的答案就是 \(f_{2n,0}\)

    轉移方程也很好寫,

    ​ $$f_{i,j}+=f_{i-1,j-1}$$ 當前作為左括號。

    ​ $$f_{i,j}+=f_{i-1,j+1}$$ 當前作為右括號並和一個左括號匹配。

分析

我們欽定兩個人分別為 \(A,B\) ,明顯的是這道題中 \(1\) 是一個比較特殊的花色,對於其他普通的花色,我可以兩個人選的一樣,然後兩個人內部進行分配使得 \(A\) 總是贏;也可以暫時讓 \(B\) 的牌多於 \(A\) ,後面拿若干張的 \(1\) 來進行彌補性配對。

我們考慮如何進行這個計數DP。

對於 \(1\) 花色特殊考慮,我們先不處理它。

對於其他任意一種顏色,我們應用第一種分配策略時,把 \(m\) 張牌均分給兩個人,首先假定這個 \(1-m\) 的序列已經降序排列好了。

那麼我們從大到小以此給兩人分配,發現如果任何時刻,我給 \(B\) 分配的牌都必須要小於等於給 \(A\) 分配的牌,才能夠滿足題設條件。

那麼這實際上就是一個卡特蘭數 \(H_{\frac{m}{2}}\),可以比較性地發現。

我們應用第二種分配時,很容易想到先選出 \(k\) (偶數)個來不進行配對之後和 \(1\) 配,然後剩下的 \(m-k\) 個按照卡特蘭數配對。

看起來沒什麼問題,實際上也確實沒問題,但在這道題的計數規則下就是錯誤的,由於我選出來不配對的幾個數實際上是歸於 \(A\) 的,這就意味著分別屬於 \(A\) 的參與匹配的集合A的暫時不參與匹配集合 的兩個數,可能會被重複計算。

反觀我們用 DP 求卡特蘭數的過程,實際上就已經把這裡我們需要的答案給計算出來了,我們想要剔除 \(k\) 個,然後剩下的配對,那麼方案數就是上面提到的 \(f_{i,k}\) ,那麼我們轉移起來也就相對來說很好想了。

\(dp_{i,j}\) 的定義是 前 \(i\) 個花色 (不包括 \(1\) ),一共剩下了 \(j\) 個的方案數。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MOD=998244353,N=600;
int dp[N][N],n,m;
inline int power(int a,int b)
{
	int ans=1;
	while(b)
	{
		if(b&1)ans=ans*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return ans%MOD;
}
inline void garbage_bin()
{
	int C[N][N],catalan[N];
	for(register int i=0;i<=m;++i)
	{
		C[i][0]=1;
		for(register int j=1;j<=i;++j)
			C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
	}
	catalan[0]=1;
	for(register int i=1;i<=m;i++)
		catalan[i]=catalan[i-1]*(4*i-2)%MOD*power(i+1,MOD-2)%MOD;
}
int cat[N][N]; 
void pre()
{
	cin>>n>>m;
	cat[0][0]=1;
	for(register int i=1;i<=m;++i)
		for(register int j=0;j<=m;++j)
		{
			if(j-1>=0)cat[i][j]+=cat[i-1][j-1],cat[i][j]%=MOD;
			cat[i][j]+=cat[i-1][j+1],cat[i][j]%=MOD;
		}
}
void solve()
{
	dp[0][0]=1;
	for(register int i=1;i<=n-1;++i)
	{
		for(register int j=0;j<=m;j+=2)
			for(register int k=0;k<=j;k+=2)
				dp[i][j]+=dp[i-1][j-k]*cat[m][k]%MOD,dp[i][j]%=MOD;	
	}

	int ans=0;
	for(register int j=0;j<=m;j+=2)
		ans+=cat[m][j]*dp[n-1][j]%MOD,ans%=MOD;
	cout<<ans;		
}
// 4  3 2 1
// 12A + 4A3B
// 13A + 4A2B
// 14A + 3A2B
// 23A + 4A1B
// 24A + 3A1B
// 34A + 2A1B 
//dp 4 2=dp 3 1 (2)+dp3 3 (1)
signed main()
{
	pre();
	solve();
	return 0;
} 

好久沒有寫過題解了。。。。

相關文章