1. 程式人生 > 其它 >AGC002(D~F)【Kruskal重構樹,博弈論,dp】

AGC002(D~F)【Kruskal重構樹,博弈論,dp】

正題


AT1998 [AGC002D] Stamp Rally【Kruskal重構樹,倍增】

https://www.luogu.com.cn/problem/AT1998

題目大意

給出\(n\)個點\(m\)條邊的一張無向圖,\(q\)次詢問兩個人分別從\(x,y\),要求總共經過\(z\)個點的情況下經過邊的最大編號的最小值。

\(1\leq n,m,q\leq 10^5\)

解題思路

直接上\(Kruskal\)重構樹然後預處理倍增陣列和子樹大小。

然後二分答案+倍增判斷就好了,這樣寫是兩個\(\log\)的,直接倍增一個\(\log\)也行但是比較麻煩。

時間複雜度:\(O(n\log^2n )\)


AT1999 [AGC002E] Candy Piles【博弈論】

https://www.luogu.com.cn/problem/AT1999

題目大意

\(n\)堆糖果,第\(i\)堆有\(a_i\)個,有如下操作

  • 取走糖果最多的那堆
  • 所有堆中各取走一個

\(1\leq n\leq 10^5,1\leq a_i\leq 10^9\)

解題思路

考慮如果現在操作的那個人一直用第一個操作會輸那麼它肯定會用第二個操作,而此時會轉換勝負態,那麼下一個人也會繼續這麼做,但是如果到最後一個且剛好是偶數那麼使用第一個操作就更優。

所以肯定存在一個數\(i\)滿足比這個位置大的都是在第二個操作被取走的,前的都是第一個位置被取走的。並且最後肯定是第二個操作。如果\(a_i\leq i\)

那麼這個位置肯定是第一個操作被取走的,因為在此之前第二個操作不可能多過第一個操作。所以找到第一個\(a_i>i\)的位置然後判斷即可。

時間複雜度:\(O(n\log n)\)

code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e5+10;
int n,a[N];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	sort(a+1,a+1+n);
	reverse(a+1,a+1+n);
	for(int i=1;i<=n;i++)
		if(a[i+1]<i+1){
			if((a[i]-i)&1)return puts("First")&0;
			int r=i;while(a[r+1]==i)r++;
			if((r-i)&1)return puts("First")&0;
			return puts("Second")&0; 
		}
	return 0;
}

AT2000 [AGC002F] Leftmost Ball【dp,組合數學】

https://www.luogu.com.cn/problem/AT2000

題目大意

\(n\)種顏色,第\(i\)種有\(k\)個,把所有排列中每種顏色的第一個染成同一種新的顏色(白色),求不同的排列數。

\(1\leq n,k\leq 2000\)

解題思路

相當於字首顏色數小於等於字首白色數,這個複雜度可以考慮平方的\(dp\)

因為其實和第一個出現的顏色有關,我們可以只保留每種顏色的前兩個來\(dp\),然後剩下的都插入到它們後面就好了,設\(f_{i,j}\)表示現在有\(i\)個白色,出現了\(j\)種顏色時的方案。

如果填白色就是直接\(f_{i-1,j}\),如果填顏色,我們可以在剩下的\(n-j+1\)個顏色中選出一個來,第二個填在目前的最前面,然後現在的空位是\(n-i-(j-1)\times (k-1)-1\)個再填\(k-2\)個就好了。

時間複雜度:\(O(n^2)\)

code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=2100,P=1e9+7;
ll n,k,inv[N*N],fac[N*N],f[N][N];
ll C(ll n,ll m)
{return fac[n]*inv[m]%P*inv[n-m]%P;}
signed main()
{
	scanf("%lld%lld",&n,&k);
	if(k==1)return puts("1")&0;
	inv[0]=fac[0]=inv[1]=1;
	for(ll i=2;i<=n*k;i++)inv[i]=P-inv[P%i]*(P/i)%P;
	for(ll i=1;i<=n*k;i++)fac[i]=fac[i-1]*i%P,inv[i]=inv[i-1]*inv[i]%P;
	f[0][0]=1;
	for(ll i=1;i<=n;i++)
		for(ll j=0;j<=i;j++)
			f[i][j]=(f[i-1][j]+f[i][j-1]*(n-j+1)%P*C(n*k-i-(j-1)*(k-1)-1,k-2)%P)%P;
	printf("%lld\n",f[n][n]);
	return 0;
}