arc058-062
E. Iroha and Haiku 考虑 $X+Y+Z$ 很小,所以想办法把一个有序数列状态压缩。
一种想法是求出原序列的后缀和,然后压后缀和的每个元素。显然我们可以通过后缀和推出一个唯一的序列。
包含 $X,Y,Z$ 就等价于该序列后缀和是原序列的子序列。
最后发现可行方案不是很好做,处理不可行然后容斥即可。复杂度 $O(n2^n)$。
查看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <iostream> #include <cstdio> #include <algorithm> #define N 50 #define M 18 #define mod 1000000007 using namespace std;int f[N][1 <<M];int main () { int n,x,y,z; scanf ("%d%d%d%d" ,&n,&x,&y,&z); int m=x+y+z,sm=(1 <<m)-1 ; int o=(1 <<(z-1 ))|(1 <<(y+z-1 ))|(1 <<(x+y+z-1 )); f[0 ][0 ]=1 ; for (int i=1 ;i<=n;i++) for (int j=1 ;j<=10 ;j++) for (int s=0 ;s<=sm;s++) if (f[i-1 ][s]) { int t=((s<<j)|(1 <<(j-1 )))&sm; if ((t&o)==o) continue ; f[i][t]=(f[i][t]+f[i-1 ][s])%mod; } int ans=1 ; for (int i=1 ;i<=n;i++) ans=10ll *ans%mod; for (int s=0 ;s<=sm;s++) ans=(ans-f[n][s]+mod)%mod; printf ("%d\n" ,ans); return 0 ; }
F. Iroha Loves Strings 做法1:首先存下到每个点为之长度为 $i$ 的最小的字符串,然后暴力转移,就可以得到一个 $O(nk^2+nm)$ 的做法,但是不太行。
考虑优化:可以发现这个做法最大的问题在于存下了所有的长度的字符串。但事实上有一些字符串不可能成为最大值。
首先我们先 $O(nK)$ 大力背包预处理可以快速算出从某个点开始可不可行。这样对于点 $i$,如果一个长度为 $i$ 的字符串严格大于长度为 $j$ 的字符串(即字典序大于且不是前后缀关系)且均有解,那么 $i$ 一定不会成为最值。换句话说,所有可能成为最值的字符串均是一个最长字符串的前缀。
这样我们维护一个单调栈,使用 SA + ST 表预处理即可达到 $O(nk+M)$ 的复杂度。
做法2:考虑我们将题目转化为:给定一个字符串和若干关键点,每次到某一个关键点就可以跳到后面的任何一个。求走恰好 $k$ 步的最小字符串。
那么一个显然的想法就是:如果可以走,就贪心往最小的走。
用 bitset 压可以转移的位置和某字符集合的位置集合,暴力转移。
复杂度 $O(nk+\frac{nm\log C}{\omega})$。
查看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <iostream> #include <cstdio> #include <cstring> #include <bitset> #define N 2010 #define M 1000001 #define K 10010 using namespace std;char s[M];int p[N],len[N];bitset<K>f[N]; bitset<M>g[65 ],w; int main () { int n,k; scanf ("%d%d" ,&n,&k); for (int i=1 ;i<=n;i++) { scanf ("%s" ,s+p[i]); len[i]=strlen (s+p[i]); p[i+1 ]=p[i]+len[i]; } f[n+1 ].set (0 ); for (int i=n;i;i--) f[i]=f[i+1 ]|(f[i+1 ]<<len[i]); for (int i=0 ;i<p[n+1 ];i++) g[32 +s[i]-'a' ].set (i); for (int i=31 ;i;i--) g[i]=g[i<<1 ]|g[i<<1 |1 ]; w.set (0 ); for (int i=1 ,u;i<=k;i++,w<<=1 ) { for (int j=1 ;j<n;j++) if (w.test (p[j])) w.set (p[j+1 ]); for (int j=1 ;j<=n;j++) if (i+len[j]-1 >k || !f[j+1 ].test (k-i-len[j]+1 )) w.set (p[j],0 ); for (u=1 ;u<32 ;u=u<<1 |!(g[u<<1 ]&w).any ()); putchar ('a' +u-32 ); w&=g[u]; } return 0 ; }
E. Children and Candies 水题。首先 $x_i$ 的具体值对答案的影响是独立的。
考虑交换求和/求积顺序后,令 $f_{i,j}$ 表示 $i$ 位置分配了 $j$,之前的所有和。
有 $f_{i,j}=\sum_{j\leq i}\left(f_{i-1,k}\sum_{p=a_i}^{b_i}{p^{j-k}}\right)$。
随便前缀和一下就 $O(n^3)$ 了。
查看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <iostream> #include <cstdio> #include <cstring> #define N 410 #define mod 1000000007 using namespace std;int f[N][N],sm[N][N];int a[N],b[N];int main () { int n,m; scanf ("%d%d" ,&n,&m); for (int i=1 ;i<=n;i++) scanf ("%d" ,&a[i]); for (int i=1 ;i<=n;i++) scanf ("%d" ,&b[i]); for (int i=1 ;i<=N-10 ;i++) for (int j=sm[0 ][i]=1 ;j<=N-10 ;j++) sm[j][i]=1ll *sm[j-1 ][i]*i%mod; for (int i=0 ;i<=N-10 ;i++) for (int j=1 ;j<=N-10 ;j++) sm[i][j]=(sm[i][j]+sm[i][j-1 ])%mod; f[0 ][0 ]=1 ; for (int i=1 ;i<=n;i++) for (int j=0 ;j<=m;j++) for (int k=0 ;k<=j;k++) f[i][j]=(f[i][j]+1ll *f[i-1 ][k]*(sm[j-k][b[i]]-sm[j-k][a[i]-1 ]+mod))%mod; printf ("%d" ,f[n][m]); return 0 ; }
F. Unhappy Hacking 以为是一道神仙题,结果是道脑筋急转弯题。
注意到按 $0,1$ 其实是本质相同的,也就是说长度相同的任意字符串出现的方案数相同。
所以直接算出长度为 $m$ 的字符串出现方案数即可。
设 $f_{i,j}$ 表示按了 $I$ 下,长度为 $j$ 的方案数。显然有 $f_{i,j}\rightarrow f_{i+1,\max(j-1,0),f_{i+1,j+1}}$。
复杂度 $O(n^2)$。
查看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <iostream> #include <cstdio> #include <cstring> #define N 5010 #define mod 1000000007 using namespace std;int f[N][N];char s[N];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 () { int n; scanf ("%d%s" ,&n,s+1 ); int m=strlen (s+1 ); f[0 ][0 ]=1 ; for (int i=1 ;i<=n;i++) for (int j=0 ;j<=n;j++) { (f[i][max (j-1 ,0 )]+=f[i-1 ][j])%=mod; f[i][j+1 ]=(f[i][j+1 ]+2ll *f[i-1 ][j])%mod; } printf ("%lld" ,1ll *f[n][m]*ksm (ksm (2 ,m))%mod); return 0 ; }
E. Tak and Hotels $\color{red}{skipped}$
F. Best Representation 第一眼幻视成串串划分,结果发现是神必题。
考虑给定的字符串如果不是全部相等,那么要么原来的串不是一个循环串,要么去掉首字母后不是一个循环串。所以答案至多为 $2$。
判掉 $1$ 和 $n$ 的情况之后,剩下的就变成询问从某个点分开左右是不是循环串。
这个直接枚举循环节然后打标记即可。复杂度 $O(n\log n)$。
查看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <iostream> #include <cstdio> #include <cstring> #define N 500010 #define mod 1019260817 using namespace std;char s[N];int hs[N],bs[N];const int B=233 ;int get (int l,int r) {return (hs[r]-1ll *hs[l-1 ]*bs[r-l+1 ]%mod+mod)%mod;}bool ban[N];int main () { scanf ("%s" ,s+1 ); int n=strlen (s+1 ); for (int i=bs[0 ]=1 ;i<=n;i++) hs[i]=(1ll *hs[i-1 ]*B+(s[i]-'a' +1 ))%mod,bs[i]=1ll *bs[i-1 ]*B%mod; bool same=true ; for (int i=2 ;i<=n;i++) if (s[i]!=s[i-1 ]) same=false ; if (same){printf ("%d\n1" ,n);return 0 ;} for (int i=1 ;i<=n;i++) { for (int j=1 ;i*j+i<=n;j++) if (get (1 ,i)==get (i*j+1 ,i*j+i)) ban[i*j+i]=true ; else break ; } for (int i=1 ;i<=n;i++) { for (int j=1 ;i*j+i<=n;j++) if (get (n-i+1 ,n)==get (n-i*j-i+1 ,n-i*j)) ban[n-i*j-i]=true ; else break ; } if (!ban[n]){puts ("1\n1" );return 0 ;} int c=0 ; for (int i=1 ;i<n;i++) if (!ban[i]) c++; printf ("2\n%d\n" ,c); return 0 ; }
E. Snuke’s Subway Trip 考虑把每个可以直接到达的点之间建一个新点,向该颜色联通块中的点连一条无向边。
容易发现新图中的最短路就是答案的两倍。
查看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 #include <iostream> #include <cstring> #include <algorithm> #include <queue> #include <vector> #include <algorithm> #define P pair<int,int> #define MP make_pair #define fi first #define se second #define N 400010 using namespace std;int nxt[N<<2 ],to[N<<2 ],head[N],cnt;void add (int u,int v) { nxt[++cnt]=head[u]; to[cnt]=v;head[u]=cnt; } int f[N];int find (int x) {return x==f[x]?f[x]:(f[x]=find (f[x]));}vector<P >g[1000010 ]; int tmp[N],tt;int idn=0 ,id[N];queue<int >q; bool vis[N];int dis[N];int bfs (int s,int t) { q.push (s);vis[s]=true ; while (!q.empty ()) { int u=q.front ();q.pop (); for (int i=head[u];i;i=nxt[i]) { int v=to[i]; if (vis[v]) continue ; vis[v]=true ;dis[v]=dis[u]+1 ; q.push (v); } } return !vis[t]?-1 :dis[t]/2 ; } int main () { int n,m; scanf ("%d%d" ,&n,&m); int mx=0 ; for (int i=1 ;i<=m;i++) { int u,v,w; scanf ("%d%d%d" ,&u,&v,&w); g[w].push_back (MP (u,v));mx=max (mx,w); } idn=n; for (int i=1 ;i<=n;i++) f[i]=i; for (int i=1 ;i<=mx;i++) if (!g[i].empty ()) { tt=0 ; for (P u:g[i]) { tmp[++tt]=u.fi;tmp[++tt]=u.se; f[find (u.fi)]=find (u.se); } for (int j=1 ;j<=tt;j++) { int u=tmp[j];if (vis[u]) continue ; vis[u]=true ; if (!id[find (u)]) id[find (u)]=++idn; id[u]=id[find (u)]; } for (int j=1 ;j<=tt;j++) vis[tmp[j]]=false ; for (int j=1 ;j<=tt;j++) { int u=tmp[j];if (vis[u]) continue ; vis[u]=true ; add (u,id[u]),add (id[u],u); } for (int j=1 ;j<=tt;j++) vis[tmp[j]]=id[tmp[j]]=0 ,f[tmp[j]]=tmp[j]; } printf ("%d\n" ,bfs (1 ,n)); return 0 ; }
F. Card Game for Three 考虑 $A$ 要获胜必须在某一时刻拿出了 $n$ 张 $A$,且 $B,C$ 分别不超过 $m,k$。
那么就有答案等于 $\displaystyle\sum_{i=n}^{s}\binom{i}{n}3^{s-i}\sum_{j=i-n-k}^m\binom{i-n}{j}$。
枚举 $i$,发现后面那个式子可以递推处理。复杂度 $O(n)$。
查看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <iostream> #include <cstdio> #include <cstring> #define N 1000010 #define mod 1000000007 using namespace std;int fac[N],inv[N];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 C (int a,int b) {return a<b || a<0 || b<0 ?0 :1ll *fac[a]*inv[b]%mod*inv[a-b]%mod;}void init (int n=N-10 ) { fac[0 ]=1 ; for (int i=1 ;i<=n;i++) fac[i]=1ll *fac[i-1 ]*i%mod; inv[n]=ksm (fac[n]); for (int i=n-1 ;i>=0 ;i--) inv[i]=1ll *inv[i+1 ]*(i+1 )%mod; } int _3[N];int main () { int n,m,k; scanf ("%d%d%d" ,&n,&m,&k); init (); int s=n+m+k,ans=0 ,r=1 ; if (m<k) swap (m,k); for (int i=_3[0 ]=1 ;i<=s;i++) _3[i]=_3[i-1 ]*3ll %mod; for (int i=0 ;i<=m+k;i++) { ans=(ans+1ll *r*C (n+i-1 ,n-1 )%mod*_3[m+k-i]%mod)%mod; r=(r*2ll -C (i,k)-C (i,i-m)+2ll *mod)%mod; } printf ("%d\n" ,ans); return 0 ; }
E. Building Cubes with AtCoDeer 考虑枚举上顶面和下底面,那么剩下的四个面就是确定的。
用一个 $\text{map}$ 记录下每种面旋转同构后的结果,注意每取一个就要删除该元素以免计重。
复杂度 $O(n^2\log n)$。
查看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <vector> #include <map> #define ll long long #define N 410 using namespace std;struct node { int p[4 ]; node (int a=0 ,int b=0 ,int c=0 ,int d=0 ){p[0 ]=a;p[1 ]=b;p[2 ]=c;p[3 ]=d;} void rot () {for (int i=0 ;i<3 ;i++) swap (p[i],p[i+1 ]);} }a[N]; bool operator <(const node a,const node b){ for (int t=0 ;t<4 ;t++) if (a.p[t]!=b.p[t]) return a.p[t]<b.p[t]; return false ; } node operator <<(node a,int p){for (int i=0 ;i<p%4 ;i++) a.rot ();return a;} node operator |(node a,node b){return node (a.p[1 ],a.p[0 ],b.p[0 ],b.p[3 ]);} vector<node> all_node (node a) {return {a,a<<1 ,a<<2 ,a<<3 };}map<node,int >mp; void ins (node u,int v) {for (auto p:all_node (u)) mp[p]+=v;}node sw[5 ]; int main () { int n; scanf ("%d" ,&n); for (int i=1 ;i<=n;i++) scanf ("%d%d%d%d" ,&a[i].p[0 ],&a[i].p[1 ],&a[i].p[2 ],&a[i].p[3 ]); for (int i=1 ;i<=n;i++) ins (a[i],1 ); ll ans=0 ; for (int i=1 ;i<=n;i++) { ins (a[i],-1 );node u=a[i]; for (int j=i+1 ;j<=n;j++) { ins (a[j],-1 ); for (auto v:all_node (a[j])) { for (int k=0 ;k<4 ;k++,u=u<<1 ,v=v<<3 ) sw[k]=u|v; bool can=true ; for (int k=0 ;k<4 && can;k++) if (!mp.count (sw[k])) can=false ; if (!can) continue ; ll res=1 ; for (int k=0 ;k<4 ;k++) res*=mp[sw[k]],ins (sw[k],-1 ); for (int k=0 ;k<4 ;k++) ins (sw[k],1 ); ans+=res; } ins (a[j],1 ); } } printf ("%lld" ,ans); return 0 ; }
F. Painting Graphs with AtCoDeer 考虑如果给定的是一棵树,那么一定不存在同构情况。
而如果给定的是一个环,这显然就是 $Polya$ 定理。
如果给定的是环加上若干条边,可以证明的是任意两条边都可以互换。
所以缩一个点双,然后判断每个点双:如果只有两个点直接乘 $k$,否则判断是那种情况。
由于 $n$ 只有 $50$,随便怎么搞都行。
查看代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 #include <iostream> #include <cstdio> #include <cstring> #include <vector> #define N 210 #define mod 1000000007 using namespace std;int nxt[N<<1 ],to[N<<1 ],head[N],cnt;void add (int u,int v) { nxt[++cnt]=head[u]; to[cnt]=v; head[u]=cnt; } vector<int >bc[N];int bt; int dfn[N],low[N],idn;int st[N],tp;void dfs (int u,int p) { low[u]=dfn[u]=++idn; st[++tp]=u; for (int i=head[u];i;i=nxt[i]) { int v=to[i]; if (!dfn[v]) { dfs (v,u); low[u]=min (low[u],low[v]); if (low[v]>=dfn[u]) { ++bt; while (st[tp]!=v) bc[bt].push_back (st[tp--]); bc[bt].push_back (st[tp--]);bc[bt].push_back (u); } } else if (v!=p) low[u]=min (low[u],dfn[v]); } } 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 fac[N],inv[N];int C (int x,int y) {return 1ll *fac[x]*inv[y]%mod*inv[x-y]%mod;}void init (int n=N-10 ) { fac[0 ]=1 ; for (int i=1 ;i<=n;i++) fac[i]=1ll *fac[i-1 ]*i%mod; inv[n]=ksm (fac[n]); for (int i=n-1 ;i>=0 ;i--) inv[i]=1ll *inv[i+1 ]*(i+1 )%mod; } bool vis[N];int gcd (int x,int y) {return y==0 ?x:gcd (y,x%y);}int main () { int n,m,k; scanf ("%d%d%d" ,&n,&m,&k); init (); for (int i=1 ;i<=m;i++) { int u,v; scanf ("%d%d" ,&u,&v); add (u,v),add (v,u); } for (int i=1 ;i<=n;i++) if (!dfn[i]) dfs (i,0 ); int ans=1 ; for (int p=1 ;p<=bt;p++) { int rd=0 ,pn=bc[p].size (); if (pn==2 ){ans=1ll *ans*k%mod;continue ;} for (int u:bc[p]) vis[u]=true ; for (int u:bc[p]) for (int i=head[u];i;i=nxt[i]) if (to[i]>u && vis[to[i]]) rd++; for (int u:bc[p]) vis[u]=false ; if (pn==rd) { int res=0 ; for (int i=1 ;i<=pn;i++) res=(res+ksm (k,gcd (i,pn)))%mod; res=1ll *res*ksm (pn)%mod; ans=1ll *ans*res%mod; } else ans=1ll *ans*C (rd+k-1 ,k-1 )%mod; } printf ("%d\n" ,ans); return 0 ; }