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