AtCoder Grand Contest 050
链接
C. Block Game
题解
容易发现,其实双方的操作是固定的。你一定会选择一个位置使得对方剩下的移动空间最小,而对方一定会选择最大的移动方式。
考虑倒过来,注意到如果用 \(B\) 将 \(S\) 序列分成若干段,那么从后往前依次需要至少 \(1,2,4,8,\cdots\) 个 \(S\)。用 \(f_{i,j}\) 表示 \(i\sim n\) 有 \(j\) 个 B 的方案数,如果当前是 \(B\),转移到 \(f_{i-2^j-1,j+1}\),如果是 \(S\) 转移到 \(f_{i-1,j}\)。
第二维最多只有 \(\log n\),复杂度 \(O(n\log n)\)。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 1000010
#define S 20
#define mod 998244353
using namespace std;
char s[N];
int f[N][S+2];
int g[N];
bool check(int x,int p){return x<=(1<<p)?g[x]==0:g[x]==g[x-(1<<p)];}
int ksm(int a,int b=mod-2)
{
int r=1;
for(;b;b>>=1)
{
if(b&1) r=1ll*r*a%mod;
a=1ll*a*a%mod;
}
return r;
}
int main()
{
scanf("%s",s+1);
int n=strlen(s+1);
for(int i=1;i<=n;i++) g[i]=g[i-1]+(s[i]=='B');
f[n][0]=1;
for(int i=n;i;i--)
for(int j=0;j<=S;j++)
if(f[i][j])
{
if(s[i]!='S' && check(i-1,j)) (f[max(i-(1<<j)-1,0)][j+1]+=f[i][j])%=mod;
if(s[i]!='B') f[i-1][j]=(f[i-1][j]+f[i][j])%mod;
}
int ans=1;
for(int i=1;i<=n;i++)
if(s[i]=='?') ans=2ll*ans%mod;
for(int j=0;j<=S;j++) ans=(ans-f[0][j]+mod)%mod;
printf("%d\n",ans);
return 0;
}
D. Shopping
题解
容易发现,一个人的胜率只和当前轮次、获胜人数与他的位置有关。
考虑 dp。设 \(g_{x,y,i,j}\) 表示还有 \(x\) 个物品没有猜,其中 \(y\) 个已经被取了,前 \(i\) 个人这一轮过后有 \(j\) 个获胜的概率。转移显然。
然后设 \(f_{x,y,i,j}\) 表示还有 \(x\) 个物品没有猜,其中 \(y\) 个已经被取了,还剩 \(i\) 个人,第 \(j\) 个人的胜率。转移可以枚举这一轮 \(j\) 前面的获胜人数和 \(j\) 后面的获胜人数,然后根据 \(g\) 转移。
总复杂度 \(O(n^6)\)。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=41,mod=998244353;
int ksm(int a,int b=mod-2)
{
int r=1;for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) r=1ll*r*a%mod;
return r;
}
int f[N][N][N][N],g[N][N][N][N],iv[N];
void init(int n=N-1){for(int i=1;i<=n;i++) iv[i]=ksm(i);}
void add(int &x,int y){x=(x+y>=mod?x+y-mod:x+y);}
int main()
{
init();
int n,k;scanf("%d%d",&n,&k);
for(int x=1;x<=n;x++)
for(int y=0;y<=x;y++)
{
g[x][y][0][0]=1;
for(int i=0;i<n;i++)
for(int j=0;j<=i && y+j<=x;j++)
add(g[x][y][i+1][j],1ll*g[x][y][i][j]*(y+j)%mod*iv[x]%mod),
add(g[x][y][i+1][j+1],1ll*g[x][y][i][j]*(x-y-j)%mod*iv[x]%mod);
}
for(int x=1;x<=k;x++)
for(int y=0;y<=x;y++)
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
{
for(int l=0;l<j && y+l<=x;l++)
{
add(f[x][y][i][j],1ll*g[x][y][j-1][l]*(x-y-l)%mod*iv[x]%mod);
for(int r=0;r<=i-j && y+l+r<=x;r++)
if(y+l+r) add(f[x][y][i][j],1ll*f[x-1][y+l+r-1][i-l-r][j-l]*g[x][y][j-1][l]%mod*g[x][y+l][i-j][r]%mod*(y+l)%mod*iv[x]%mod);
}
}
for(int i=1;i<=n;i++) printf("%d\n",f[k][0][n][i]);
return 0;
}
E. Three Traffic Lights
题解
令 \(m_1=r_1+b_2\),\(m_2,m_3\) 同理。
考虑转化为求满满足 \(i<r_1,j<r_2,k<r_3\) 的合法 \((i,m_1,j,m_2,k,m_3)\) 对数,要求存在 \(x\) 满足 \(x\bmod m_1=i\),\(m_2,m_3\) 同理。
考虑 \(m_1\) 中某个质数 \(p\)。容易发现,如果 \(p\) 不是 \(m_2,m_3\) 的因数,那么令 \(m'=\frac{m_1}{p}\),\((i,m_1,j,m_2,k,m_3)\) 合法当且仅当 \((i\bmod m',m_1\bmod m',j,m_2,k,m_3)\) 合法。
对 \(m_1,m_2,m_3\) 分别处理,可以拆分成至多 \(8\) 个子问题。对于每个子问题,最后 \((m_1,m_2,m_3)\) 总是能表示成 \(m_1=gab,m_2=gbc,m_3=gac\) 的形式。不妨假设 \(a\leq b\leq c\),那么满足 \(m_2,m_3\) 限制的区间个数是 \(O(a+b)=O(\sqrt V)\) 的,直接双指针即可。
复杂度 \(O(\sqrt V)\)。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 2000010
using namespace std;
typedef long long ll;
const int mod=998244353;
ll gcd(ll x,ll y){return y==0?x:gcd(y,x%y);}
ll chk(ll m1,ll m2,ll m3){ll g=m1;g/=gcd(g,m2),g/=gcd(g,m3);return m1/g;}
ll calc(ll x,ll g,ll m){return x/m*(g+1)+min(g,x%m);}
ll res=1;
int ksm(int a,int b=mod-2)
{
int r=1;
for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) r=1ll*r*a%mod;
return r;
}
int solve(ll g1,ll m1,ll g2,ll m2,ll g3,ll m3)
{
ll m=chk(m1,m2,m3);if(m!=m1) return (solve(g1%m,m,g2,m2,g3,m3)+g1/m%mod*solve(m-1,m,g2,m2,g3,m3)%mod)%mod;
m=chk(m2,m1,m3);if(m!=m2) return (solve(g1,m1,g2%m,m,g3,m3)+g2/m%mod*solve(g1,m1,m-1,m,g3,m3)%mod)%mod;
m=chk(m3,m1,m2);if(m!=m3) return (solve(g1,m1,g2,m2,g3%m,m)+g3/m%mod*solve(g1,m1,g2,m2,m-1,m)%mod)%mod;
if(m1<m2) swap(m1,m2),swap(g1,g2);
if(m1<m3) swap(m1,m3),swap(g1,g3);
if(m2<m3) swap(m2,m3),swap(g2,g3);
ll g=m1/gcd(m1,m2)*m2;g=g/gcd(g,m3)*m3;
ll p1=0,p2=0,res=0;
while(p1<g || p2<g)
{
ll l=max(p1,p2),r=min(p1+g1,p2+g2);
if(l<=r) res+=calc(r,g3,m3)-calc(l-1,g3,m3);
if(p1+g1<p2+g2) p1+=m1; else p2+=m2;
}
return (m1%mod)*(m2%mod)%mod*(m3%mod)%mod*(res%mod)%mod*ksm(g%mod)%mod;
}
int main()
{
ll m1,g1,m2,g2,m3,g3;
scanf("%lld%lld%lld%lld%lld%lld",&g1,&m1,&g2,&m2,&g3,&m3);
m1+=g1,m2+=g2,m3+=g3,g1--,g2--,g3--;
printf("%d\n",solve(g1,m1,g2,m2,g3,m3));
return 0;
}
F. NAND Tree
题解
神仙题。
首先看到答案对 \(2\) 取模想到互相抵消,一种方法就是把 \(n-1\) 次操作每两个划一组。假设 \(n\) 是奇数,容易发现,如果有某一组内的两次操作交换后答案不变,那么这一整个方案都会被抵消。
那么要使得每一组内两次操作交换后答案变化,这两次操作必须有一个节点重合。不妨假设这三个点是 \(a,b,c\),操作是 \(((a,b),c)\) 和 \((a,(b,c))\),容易发现,要使得最后两种操作的结果不同,当且仅当 \(a\neq c\),并且此时与 \(b\) 的值无关。那么不妨要求这里 \(a=1,c=0\)。
考虑每次进行完这个操作后,缩成的点权值可以是 \(0/1\) 中任意一个。容易发现,如果某次操作的 \(a,b,c\) 中 \(b\) 点权任意或者 \(a,c\) 点权同时任意,那么这次操作结果一定会被抵消。这样如果树上存在两个点权任意的点,那么无论怎么操作都会抵消。所以最后不会被抵消的方案必须时刻对一开始生成的特殊点进行操作。
那么考虑枚举第一次操作中 \(a\) 的位置,以它为根,那么每次操作实际上就是选择一条以根为端点的长度为 \(2\) 的链,然后将这条链缩起来。注意到只有根是特殊点,所以这个过程的方案数与子节点的权值无关。
考虑直接计算可能并不方便。但是注意到一件事情:假如在某一时刻根连出了两个子树,那么方案数一定会被抵消,因为交换两个子树的执行顺序不会影响结果。这样我们可以直接用树的拓扑序来代替原先的方案数。而拓扑序方案数就是 \(\frac{n!}{\prod siz_i}\),可以直接统计每个数中 \(2\) 的幂次得到最后对 \(2\) 取模的结果。
对于 \(n\) 是偶数的情况,强行枚举第一条边。复杂度 \(O(n^3)\)。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int N=310;
vector<int>g[N];int siz[N],_2[N],n;
void dfs(int u,int p,int x,int y,int &tt)
{
siz[u]=1;
for(int v:g[u]) if(v!=p) dfs(v,u,x,y,tt),siz[u]+=siz[v];
if((u==x && p==y) || (u==y && p==x)) siz[u]--;
else tt-=_2[siz[u]];
}
int ans=0,a[N];
void solve(int x,int y)
{
for(int i=1;i<=n;i++) if(i!=y && a[i])
{
int tt=0;for(int j=1;j<=n-!!y;j++) tt+=_2[j];
dfs(i,0,x,y,tt);
ans^=!tt;
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) _2[i]=i&1?0:_2[i>>1]+1;
for(int i=1,x,y;i<n;i++) scanf("%d%d",&x,&y),g[x].push_back(y),g[y].push_back(x);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
if(n&1) solve(0,0);
else
for(int u=1;u<=n;u++)
for(int v:g[u]) if(u<v)
{
int w=a[u];a[u]=!(a[u]&a[v]);
solve(u,v);
a[u]=w;
}
printf("%d\n",ans);
return 0;
}

浙公网安备 33010602011771号