AtCoder Regular Contest 171

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

最近慢慢要开始准备给新一届的暑假前集训搬题了,可能能补题的时间会变少一些,但还是尽量能补就补吧

posted @ 2024-03-07 15:52  空気力学の詩  阅读(21)  评论(0编辑  收藏  举报