1. 程式人生 > 實用技巧 >CSP-S2019 題解

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;
}