CSP-S2019 题解

T1 格雷码

考虑第\(i\)位,第\(0\)位分别为\(011001100\dots\),第\(1\)位分别为\(001111000011110000\dots\),从而第\(i\)位等于\(k\oplus\left\lfloor\dfrac k2\right\rfloor\)的第\(i\)位。使用unsigned long long存贮,按位输出即可。

unsigned long long n,k;
int res[65],i;

int main()
{
	cin>>n>>k;
	k=k^(k>>1ull);
	
	while(k)
	{
		res[i++]=k&1ull;
		k>>=1ull;
	}
	
	for(int i=n-1;~i;--i)
		printf("%d",res[i]);

	puts("");

        return 0;
}

T2 括号树

先考虑链的情况。我们设\(s_i\)为以第\(i\)位为结尾的合法括号串的数量,维护一个栈\(S\),当第\(i\)位为(时入栈,当第\(i\)位为)时匹配栈顶\(t\)(若栈为空则直接令\(s_i=0\)),该位的\(s\)值就等于匹配到的栈顶\(t\)的前一位的\(s\)值加一,即\(s_i=s_{t-1}+1\),同时弹出栈顶。最终的答案\(\mathrm{ans}_i\)就等于\(s_i\)的前缀和。

进一步地,一棵树可以拆成由根到叶子的若干条链。我们按照链的方式即可计算\(s_i\)\(\mathrm{ans}_i\),只不过每个节点\(i\)的前一个节点变成了\(\mathrm{fa}_i\),dfs时注意将栈回溯即可。

const int Maxn=5e5+7;

typedef long long LL;

char str[Maxn];
int n,stac[Maxn],top,fa[Maxn];
LL f[Maxn],ans[Maxn];

struct Edge
{
	int nxt,to;
}e[Maxn];

int edge_cnt,head[Maxn];

inline void add_edge(int u,int v)
{
	e[++edge_cnt].nxt=head[u];
	e[edge_cnt].to=v;
	head[u]=edge_cnt;
}

inline void DFS(int u)
{
	int tmp=0;
	
	if(str[u]==')')
	{
		if(top)
		{
			tmp=stac[top];
			f[u]=f[fa[tmp]]+1;
			--top;
		}
	}
	else if(str[u]=='(')
		stac[++top]=u;
	
	ans[u]=ans[fa[u]]+f[u];
	
	for(int i=head[u];i;i=e[i].nxt)
		DFS(e[i].to);
	
	if(tmp)
		stac[++top]=tmp;
	else if(top)
		--top;
}

int main()
{
	scanf("%d",&n);
	scanf("%s",str+1);
	
	for(int i=2,f;i<=n;++i)
		scanf("%d",&f),
		fa[i]=f,
		add_edge(f,i);
	
	DFS(1);
	
	LL res=0;
	
	for(int i=1;i<=n;++i)
		res^=(1LL*i*ans[i]);
	
	printf("%lld\n",res);
	
	return 0;
}

T4 Emiya家今天的饭

先考虑\(m\le 3\)的部分分。设\(f(i,A,B,C)\)表示处理到前\(i\)行,三种食材分别选了\(A,B,C\)个时的方案数。则

\[f(i,A,B,C)\leftarrow f(i-1,A,B,C)+a_{i1}\cdot f(i-1,A-1,B,C)+a_{i2}\cdot f(i-1,A,B-1,C)+a_{i3}\cdot f(i-1,A,B,C-1) \]

边界为\(f(0,0,0,0)=1\)。最后的答案为

\[\mathrm{ans}=\sum_{A,B,C\le n}f(n,A,B,C),\qquad \max\{A,B,C\}\le\dfrac{A+B+C}2,\ A+B+C>0 \]

可以倒序枚举\(A,B,C\)来压掉\(i\)这一维。总复杂度\(O(n^4)\)

\(m\ge3\)时,\(m\)迅速增大,无法使用\(O(n^{m+1})\)的做法。考虑容斥,合法方案数就等于总方案数减去不合法方案数。

我们设\(f(i,j)\)表示处理到前\(i\)行,选了\(j\)种食材的总方案数。则

\[f(i,j)\leftarrow f(i-1,j)+s_i\cdot f(i-1,j-1) \]

其中\(s_i\)为第\(i\)行的前缀和,边界为\(f(0,0)=1\)。总方案数为

\[S=\sum_{j=1}^n f(n,j) \]

可以倒序枚举\(j\)来压掉\(i\)这一维。这部分的复杂度\(O(nm)\)

注意到,不合法的方案中有且仅有一种食材\(c\)的出现次数大于总菜数的一半。\(O(m)\)枚举\(c\),对于给定的\(c\),设\(g_c(i,j,k)\)为处理到前\(i\)行,其中第\(c\)种食材选了\(j\)种方式,其余食材选了\(k\)种方式时的方案数,则

\[g_c(i,j,k)\leftarrow g_c(i-1,j,k)+a_{ic}\cdot g_c(i-1,j-1,k)+(s_i-a_{ic})\cdot g_c(i-1,j,k-1) \]

边界为\(g_c(0,0,0)=1\)。总不合法的方案数即为

\[S'=\sum_{c=1}^m\sum_{j>k}g_c(n,j,k) \]

同理可以倒序枚举\(j,k\)来压掉\(i\)这一维,最终答案即为\(\mathrm{ans}=S-S'\)。这部分的复杂度\(O(n^2m)\),总复杂度\(O(n^2m)\)

注意到我们并不在意\(j,k\)的具体值,可用它们的差值\(\delta=j-k\)来描述状态,则\(g_c\)的转移方程变为

\[g_c(i,\delta)\leftarrow g_c(i-1,\delta)+a_{ic}\cdot g_c(i-1,\delta-1)+(s_i-a_{ic})\cdot g_c(i-1,\delta+1) \]

边界为\(g_c(0,0)=1\)。总不合法的方案数即为

\[S'=\sum_{c=1}^m\sum_{\delta>0}g_c(n,\delta) \]

总复杂度降为\(O(nm)\)。可以将\(\delta\)加上\(n\)来防止越界。

const int Maxn=107;
const int Maxm=2e3+7;

const int Mod=998244353;

typedef long long LL;

LL f[Maxn],g[Maxn][Maxn<<1],ans;
int n,m;
LL a[Maxn][Maxm],s[Maxn];

signed main()
{
	scanf("%d%d",&n,&m);
	
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			scanf("%lld",&a[i][j]),
			s[i]=(s[i]+a[i][j])%Mod;

	f[0]=1;
	
	for(int i=1;i<=n;++i)
		for(int j=i;j;--j)
			f[j]=(f[j]+1LL*s[i]*f[j-1])%Mod;
	
	for(int j=1;j<=n;++j)
		ans=(ans+f[j])%Mod;
	
	for(int c=1;c<=m;++c)
	{
		memset(g,0,sizeof(g));
	
		g[0][n]=1;
		
		for(int i=1;i<=n;++i)
			for(int d=1;d<=n+i;++d)
				g[i][d]=(g[i][d]+g[i-1][d]+1LL*g[i-1][d-1]*a[i][c]+1LL*g[i-1][d+1]*(s[i]-a[i][c]))%Mod;
		
		for(int d=n+1;d<=n*2;++d)
				ans=(ans-g[n][d]+Mod)%Mod;
	}
	
	printf("%lld\n",ans);

        return 0;
}
posted @ 2020-09-19 16:13  Anverking  阅读(273)  评论(0编辑  收藏  举报