AtCoder Regular Contest 171

空気力学の詩發表於2024-03-07

Preface

小補一場ARC,D還是很有意思的但想了半天不會,鑑定為純純的彩筆


A - No Attacking

考慮怎麼放rook能使得留下來能放pawn的格子數量最多,手玩一下會發現先按照\((2,2),(4,4),(6,6),\cdots\)的順序放,放滿後再接著放\((1,1),(3,3),(5,5),\cdots\)是最優的

手玩一下可以得出在放了\(A\)個rook後最多能放的pawn的個數:

  • \(2A\le N\)時,個數為\((N-A)\times (A+\lceil\frac{N-2A}{2}\rceil)\)
  • \(2A>N\)時,個數為\((N-A)^2\)
#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef __int128 i128;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
int t,n,a,b;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		scanf("%d%d%d",&n,&a,&b); int lim;
		if (a>n) { puts("No"); continue; }
		if (2*a<=n) lim=(n-a)*(a+(n-2*a+1)/2); else lim=(n-a)*(n-a);
		puts(b<=lim?"Yes":"No");
	}
	return 0;
}

B - Chmax

原問題可以被轉化為,對於每個點\(i\),若\(i<p_i\)則連一條\(i\to p_i\)的有向邊,否則不連邊;最後要從每個點出發都能走到\(a_i\)

首先考慮顯然無解的情況,比較顯然的時當有\(a_i<i\)的元素出現時一定不合法

另外我們把所有\(a_i\)相同的數看作一類,並將一類的數中下標最大的記為end,則所有end位置必須滿足\(a_i=i\)

接下來考慮計算方案數,不妨考慮從前往後確定每個數的\(p_i\)取值數量,分類討論:

  • \(i\)的後面還有和它一類的點\(j\)(即\(i\)不是end),則\(i\)只能連向\(j\),此時方案數唯一
  • \(i\)的後面沒有和它一類的點\(j\)(即\(i\)end),則\(i\)可以在下標\(\le i\)的點中選擇一個沒有入度的點作為\(p_i\)

考慮第二種情況的方案數很容易計算,總複雜度\(O(n)\)

#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef __int128 i128;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
const int N=200005,mod=998244353;
int n,a[N],d[N],bkt[N],nxt[N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]),bkt[i]=n+1;
	for (i=1;i<=n;++i) if (a[i]<i) return puts("0"),0; 
	for (i=n;i>=1;--i) nxt[i]=bkt[a[i]],bkt[a[i]]=i;
	for (i=1;i<=n;++i) if (nxt[i]>n&&a[i]!=i) return puts("0"),0;
	int ans=1,end=0; for (i=1;i<=n;++i)
	{
		d[i]+=d[i-1];
		if (nxt[i]<=n) { ++d[nxt[i]]; continue; }
		ans=1LL*ans*(i-d[i]-end)%mod; ++end;
	}
	return printf("%d\n",ans),0;
}

C - Swap on Tree

很有意思的一個Counting題,算是那種不難但是需要想一下的型別

考慮兩種方案本質相同的充要條件,首先很容易發現一個必要條件就是選擇需要操作的邊集要相同,這個手玩一下會發現很顯然

但顯然操作的順序也會影響,不妨拿一個菊花圖為例,不難發現如選擇了中心相鄰的\(x\)條邊,則共有\(x!\)種本質不同的結果序列

考慮將這個結論推廣,不難發現總方案數等於\(\sum_{E'\in E}\prod_{i\in V} deg_{E',i}!\),即對於某個確定的選邊方案,其貢獻為每個點度數(只考慮選中的邊)的階乘

要求這個也很簡單,不妨統一在向下的方向上統計貢獻,設\(f_{i,0/1}\)表示處理了\(i\)的子樹,且\(i\)與其父親的邊是否要選中的貢獻

對於每個點\(x\),對其兒子\(y\)\(f_{y,0/1}\)跑一個類似於揹包的東西即可合併方案數,然後統計\(x\)的貢獻時注意分是否要往上連討論一下即可

總複雜度\(O(n^2)\)

#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef __int128 i128;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
const int N=3005,mod=998244353;
int n,x,y,f[N][2],fact[N]; vector <int> v[N];
inline void DFS(CI now=1,CI fa=0)
{
	for (auto to:v[now]) if (to!=fa) DFS(to,now);
	RI i; int m=v[now].size();
	static int g[N],tmp[N]; memset(g,0,sizeof(g)); g[0]=1;
	for (auto to:v[now]) if (to!=fa)
	{
		for (i=0;i<=m;++i) tmp[i]=g[i],g[i]=0;
		for (i=0;i<=m;++i)
		{
			(g[i]+=1LL*tmp[i]*f[to][0]%mod)%=mod;
			(g[i+1]+=1LL*tmp[i]*f[to][1]%mod)%=mod;
		}
	}
	for (i=0;i<=m;++i)
	{
		(f[now][0]+=1LL*g[i]*fact[i]%mod)%=mod;
		(f[now][1]+=1LL*g[i]*fact[i+1]%mod)%=mod;
	}
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i; for (scanf("%d",&n),i=1;i<n;++i)
	scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
	for (fact[0]=i=1;i<=n;++i) fact[i]=1LL*fact[i-1]*i%mod;
	return DFS(),printf("%d",f[1][0]),0;
}

D - Rolling Hash

感覺不難的一個題,但我犯病了想著用字首和算Hash值的時候被常見的形式固定死了,導致沒法轉化出關鍵一步直接GG

首先由於題目中給一個串的Hash值定義和我們熟悉的字串Hash完全相同,因此考慮設Hash函式的字首和陣列為\(h_i\),則\(hash(l,r)=h_r-h_{l-1}\times B^{r-l+1}\),由於式子中含有\(B^{r-l+1}\)很難處理

其實這就完全陷入了陷阱之中,我們不妨稍微修改下Hash函式字首和的定義,直接令\(h'_i=(\sum_{j=1}^i x_j B^{n-j})\bmod P\)(注意和傳統的定義的區別就是直接把每一個位置的位權定死)

這樣做的好處就是可以發現此時\(hash(l,r)=(h'_r-h'_{l-1})\times \frac{1}{B^{n-r}}\),而\(hash(l,r)\ne 0\)的充要條件就變成\(h'_{l-1}\ne h'_r\)了,這個條件就非常好

不難發現當確定了\(\{h'_i\}\)後自然就確定了\(\{x_i\}\),而且二者可以相互轉化,因此只需要判斷是否有合法的\(\{h'_i\}\)序列即可

不妨把一個限制\((l,r)\)看作一條\(l-1\leftrightarrow r\)的雙向邊,則題目等價於判定能否用\(P\)種顏色給某個圖染色,使得任意相鄰兩點的顏色不同

這個問題有個經典的DP做法,設\(f_{mask}\)表示將點集為\(mask\)及其匯出子圖染色所需的最小顏色數,每次轉移的時候列舉\(mask\)的子集\(S\),若\(S\)是獨立集則轉移\(f_{mask\oplus S}+1\to f_{mask}\)

總複雜度\(O(3^n)\),應該可以用類似sosdp或者FMT之類的科技最佳化到\(O(2^n\times n^{O(1)})\),但直接上列舉子集已經足以透過此題

#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef __int128 i128;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
const int N=17;
int P,B,n,m,l,r,E[N][N],g[1<<N],f[1<<N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i,j,k; scanf("%d%d%d%d",&P,&B,&n,&m);
	for (i=1;i<=m;++i) scanf("%d%d",&l,&r),E[l-1][r]=E[r][l-1]=1;
	for (i=1;i<(1<<n+1);++i)
	for (g[i]=1,j=0;j<=n;++j) if ((i>>j)&1)
	for (k=j+1;k<=n;++k) if ((i>>k)&1) if (E[j][k]) g[i]=0;
	for (i=1;i<(1<<n+1);++i)
	for (f[i]=1e9,j=i;j;j=(j-1)&i)
	if (g[j]) f[i]=min(f[i],f[i^j]+1);
	return puts(f[(1<<n+1)-1]<=P?"Yes":"No"),0;
}

Postscript

最近慢慢要開始準備給新一屆的暑假前集訓搬題了,可能能補題的時間會變少一些,但還是儘量能補就補吧