1. 3721 Smuggling Marbles
大意: 给定$n+1$节点树, $0$为根节点, 初始在一些节点放一个石子, 然后按顺序进行如下操作.
- 若$0$节点有石子, 则移入盒子
- 所有石子移向父亲节点
- 把所有不少于$2$个石子的节点的石子丢掉
- 若树上还有石子,则返回第一步
对于所有$2^{n+1}$种初始放石子的方案, 求出最后盒子中石子总数之和.
长链剖分, 这道以后再写
2. 3727 Prefix-free Game
两个串$s,t$合法要满足 $s$不为$t$的前缀且$t$不为$s$的前缀.
一个字符串集合合法要求满足 每个串长度范围$[1,L]$, 每个串只由$01$组成, 任意两串合法.
给定合法字符串集$S$, 两人轮流操作, 每次添加一个字符串, 要求添加后$S$仍然合法, 不能操作则输. 求最后胜负情况.
假设初始$S$为空的情况. 那么初始状态可以看做两棵深度为$L$的满二叉树(因为不能取空串).
每步操作相当于选一个节点$x$, 然后删去$x$的子树以及$x$到根的链. 可以发现删完一定分裂成多颗满二叉树, 所以这样状态就只与二叉树的深度有关, 可以得到
$$SG_{x}=mex\{0,SG_{x-1},SG_{x-1}\oplus SG_{x-2},...,SG_{x-1}\oplus ...\oplus SG_{1}\}$$
打表可以发现$SG_{x}=lowbit(x)$.
所以对于给定初始字符串集合$S$的情况, 用$trie$模拟求出初始$SG$值即可.
#include <iostream> #include <cstdio> #define REP(i,a,n) for(int i=a;i<=n;++i) using namespace std; typedef long long ll; const int N = 1e6+10; int n, T, tot; ll L, ans; struct {int ch[2];} tr[N<<2]; char s[N]; void add(int &o, char *s) { if (!o) o = ++tot; if (*s) add(tr[o].ch[*s=='1'],s+1); } void dfs(int o, ll d) { if (!o) ans ^= d&-d; else dfs(tr[o].ch[0],d-1),dfs(tr[o].ch[1],d-1); } int main() { scanf("%d%lld", &n, &L); REP(i,1,n) scanf("%s", s),add(T,s); dfs(T,L+1); puts(ans?"Alice":"Bob"); }
3. 3939 Strange Nim
大意: $n$堆石子, 第$i$堆初始$A_i$, 有一个系数$K_i$, 每次操作假设第$i$堆有$X$个石子, 那么可以拿走的石子范围为$[1,\lfloor\frac{X}{K_i}\rfloor]$. 两人轮流操作, 求最后胜负情况.
打表可以发现$x\%k==0$时$, sg(x,k)=\lfloor\frac{x}{k}\rfloor$.
否则$sg(x,k)=sg(x-\lceil\frac{x}{k}\rceil,k)$.
通过同时减去相同的$\lceil\frac{x}{k}\rceil$来优化, 复杂度就为$O(\sqrt{k})$
#include <iostream> #include <cstdio> using namespace std; int sg(int x, int k) { if (x%k==0) return x/k; int t = x/k+1; return sg(x-(x%k+t-1)/t*t,k); } int main() { int n; scanf("%d", &n); int ans = 0; while (n--) { int a, k; scanf("%d%d", &a, &k); ans ^= sg(a,k); } puts(ans?"Takahashi":"Aoki"); }
4. 2044 Teleporter
大意: $n$个点, 点$i$后继为$a_i$, 每个点都可以到达$1$, 求最少修改多少后继使得每个点恰好走$k$步能到达点$1$.
$a_1$必须为$1$, 否则$1$和$a_1$一定不能满足条件, 然后$dfs$从叶子往上贪心.
#include <iostream> #include <cstdio> #include <queue> #define REP(i,a,n) for(int i=a;i<=n;++i) #define pb push_back using namespace std; const int N = 1e6+10; int n, k, ans, a[N], f[N]; vector<int> g[N]; void dfs(int x) { f[x] = 1; for (int y:g[x]) dfs(y), f[x] = max(f[x], f[y]+1); if (f[x]==k&&a[x]!=1) ++ans,f[x]=0; } int main() { scanf("%d%d", &n, &k); REP(i,1,n) scanf("%d", a+i); ans = a[1]!=1; a[1] = 1; REP(i,2,n) g[a[i]].pb(i); dfs(1); printf("%d\n", ans); }
5. 2000 Leftmost Ball
大意: 给定$n$个颜色的球, 每种$k$个, 任意排列后将每种球第一个颜色染为$0$, 求能得到多少种序列.
设$f_{i,j}$为当前放了$i$个$0$, $j$种颜色的方案数.
从左到右枚举最前面的空位应该放白球还是放彩球, 若放彩球则将剩余彩球直接分配下去.
#include <iostream> #include <cstdio> #define REP(i,a,n) for(int i=a;i<=n;++i) #define PER(i,a,n) for(int i=n;i>=a;--i) using namespace std; typedef long long ll; const int P = 1e9+7; ll inv(ll x){return x<=1?1:inv(P%x)*(P-P/x)%P;} const int N = 2010, M = 4e6+10; int n, k, dp[N][N]; int fac[M], ifac[M]; int C(int n, int m) { if (n<m) return 0; return (ll)fac[n]*ifac[m]%P*ifac[n-m]%P; } int main() { fac[0]=1; REP(i,1,M-1) fac[i]=(ll)fac[i-1]*i%P; ifac[M-1]=inv(fac[M-1]); PER(i,0,M-2) ifac[i]=(ll)ifac[i+1]*(i+1)%P; scanf("%d%d", &n, &k); if (k==1) return puts("1"),0; dp[0][0] = 1; REP(i,0,n) REP(j,0,i) { dp[i+1][j] = (dp[i+1][j]+dp[i][j])%P; dp[i][j+1] = (dp[i][j+1]+(ll)C(n*k-j*(k-1)-i-1,k-2)*dp[i][j])%P; } int ans = (ll)dp[n][n]*fac[n]%P; printf("%d\n", ans); }
6. 2020 Unbalanced
大意: 若一个串满足长度不少于$2$且超过一半的字符相同, 则称它为不平衡串. 给定串$s$, 要求输出$s$的任意一个不平衡子串.
众数的套路题. 枚举字符$x$作为众数的情况, $x$看做$1$, 其余字符看做$-1$, 那么就等价于找一个和大于零的区间.
#include <iostream> #include <cstdio> #include <string.h> #define REP(i,a,n) for(int i=a;i<=n;++i) #define x first #define y second using namespace std; typedef pair<int,int> pii; const int N = 1e6+10; int n, f[N][30]; pii mi[30]; char s[N]; int main() { scanf("%s", s+1); n = strlen(s+1); REP(i,1,n) { memcpy(f[i],f[i-1],sizeof f[0]); REP(j,'a','z') { if (s[i]==j) ++f[i][j-'a']; else --f[i][j-'a']; } if (i>1) { REP(j,0,25) if (f[i][j]-mi[j].x>0) { return printf("%d %d\n",mi[j].y+1,i),0; } } REP(j,0,25) mi[j] = min(mi[j], pii(f[i-1][j],i-1)); } puts("-1 -1"); }
看了其他人题解发现有更简便做法, 因为只需要找一个, 所以直接判断是否存在$XYX$或$XX$这种即可.
7. 2021 Children and Candies
大意: $n$个人分$C$块糖. 定义函数$f(x_1,...,x_n)$, 第$i$个人若分$a$块糖, 则高兴度为$x_i^a$, $f$的值为所有人高兴度的乘积. 给定序列$A,B$, 求$\sum\limits_{x_1=A_1}^{B_1}\sum\limits_{x_2=A_2}^{B_2}\cdots\sum\limits_{x_n=A_n}^{B_n}f(x_1,x_2,...,x_n)$
简单dp题, 设$dp_{i,x}$为前$i$个人分$x$块糖的答案, 可以得到$dp_{i,x}=\sum\limits_{A_i\le k\le B_i}\sum\limits_{y\le x}dp_{i-1,y}k^{x-y}$.
然后前缀优化一下.
#include <iostream> #include <cstdio> #define REP(i,a,n) for(int i=a;i<=n;++i) using namespace std; typedef long long ll; const int P = 1e9+7, INF = 0x3f3f3f3f; const int N = 410; int n, c, a[N], b[N], dp[N][N]; int po[N][N], sum[N][N]; int main() { REP(i,1,N-1) { po[i][0] = 1; REP(j,1,N-1) po[i][j] = (ll)po[i][j-1]*i%P; } REP(i,0,N-1) { sum[i][0] = 1; REP(j,1,N-1) sum[i][j] = (sum[i][j-1]+po[j][i])%P; } cin>>n>>c; REP(i,1,n) cin>>a[i]; REP(i,1,n) cin>>b[i]; dp[0][0] = 1; REP(i,1,n) REP(x,0,c) REP(y,0,x) { int ret = sum[x-y][b[i]]-sum[x-y][a[i]-1]; dp[i][x] = (dp[i][x]+(ll)dp[i-1][y]*ret)%P; } int ans = dp[n][c]; if (ans<0) ans += P; printf("%d\n", ans); }
8. 2022 Unhappy Hacking
大意: 键盘有三个键$0,1$和退格, 求按$n$次以后得到字符串$s$的方案数
显然答案只与$n$有关, 所以求出能得到的所有串的方案数最后除以$2^{|s|}$即可
#include <iostream> #include <cstdio> #define REP(i,a,n) for(int i=a;i<=n;++i) using namespace std; typedef long long ll; const int P = 1e9+7; ll qpow(ll a,ll n) {ll r=1%P;for (a%=P;n;a=a*a%P,n>>=1)if(n&1)r=r*a%P;return r;} ll inv(ll x){return x<=1?1:inv(P%x)*(P-P/x)%P;} const int N = 5010; int n, m, dp[N][N]; char s[N]; void add(int &x, ll y) {x = (x+y)%P;} int main() { scanf("%d%s", &n, s+1); m = strlen(s+1); dp[0][0] = 1; REP(i,0,n) REP(j,0,i) if (dp[i][j]) { add(dp[i+1][j+1],2*dp[i][j]); add(dp[i+1][max(j-1,0)],dp[i][j]); } int ans = (ll)dp[n][m]*inv(qpow(2,m))%P; printf("%d\n", ans); }
9. 2070 Card Game for Three
大意: $A,B,C$三个人初始$n,m,k$张牌, 每张牌上是三个人名字. 每个人出牌顺序固定, 每轮出一张牌, 然后牌上写的人接着出. 谁先出完谁赢. 对于所有$3^{n+m+k}$中出牌顺序, 求先手胜利方案数.
显然对于一个长度为$x$的出牌序列, 对应$3^{n+m+k-x}$种方案.
只需要考虑出$n$张$A$,$i$张$B$,$j$张$C$, 且最后一张为$A$的方案数, 有
$$\begin{align} ans &=\sum\limits_{i=0}^m\sum\limits_{j=0}^k 3^{m+k-i-j}\frac{(n+i+j-1)!}{(n-1)!i!j!} \notag \\ &= \frac{3^{m+k}}{(n-1)!}\sum\limits_{i=0}^m\frac{3^{-i}}{i!}\sum\limits_{j=0}^k\frac{3^{-j}}{j!}(n+i+j-1)! \notag \end{align}$$
记$f(i)=\sum\limits_{j=0}^k\frac{3^{-j}}{j!}(n+i+j-1)!$
有
$$\begin{align} f(i+1)-f(i) &= \sum\limits_{j=0}^k\frac{3^{-j}}{j!}(n+i+j-1)!(n+i+j-1) \notag \\ &= f(i)(n+i-1)+\sum\limits_{j=0}^k \frac{3^{-j}j}{j!}(n+i+j-1)! \notag \\ &= f(i)(n+i-1)+3^{-1}(f(i+1)-\frac{3^{-k}}{k!}(n+i+k)!) \notag \end{align}$$
所以
$$f(i+1)=\frac{3}{2}f(i)(n+i)-\frac{3^{-k}}{2}\frac{(n+i+k)!}{k!}$$
然后就可以$O(n)$做了
#include <iostream> #include <cstdio> #define REP(i,a,n) for(int i=a;i<=n;++i) #define PER(i,a,n) for(int i=n;i>=a;--i) using namespace std; typedef long long ll; const int N = 1e6+10, P = 1e9+7, inv2 = (P+1)/2; int inv(int x){return x<=1?1:inv(P%x)*(P-(ll)P/x)%P;} int n,m,k,fac[N],ifac[N],po[N],ipo[N]; int main() { ifac[0]=fac[0]=po[0]=ipo[0]=1; REP(i,1,N-1) { fac[i]=fac[i-1]*(ll)i%P; po[i]=po[i-1]*3ll%P; } ifac[N-1]=inv(fac[N-1]),ipo[N-1]=inv(po[N-1]); PER(i,1,N-2) { ifac[i]=ifac[i+1]*(i+1ll)%P; ipo[i]=ipo[i+1]*3ll%P; } cin>>n>>m>>k; int ans = 0, ret = 0; REP(j,0,k) ret = (ret+(ll)ipo[j]*ifac[j]%P*fac[n+j-1])%P; REP(i,0,m) { ans = (ans+(ll)ipo[i]*ifac[i]%P*ret)%P; ret = 3ll*inv2%P*ret%P*(n+i)%P; ret = (ret-(ll)ipo[k]*inv2%P*fac[n+i+k]%P*ifac[k])%P; } ans = (ll)ans*po[m+k]%P*ifac[n-1]%P; if (ans<0) ans += P; printf("%d\n", ans); }
10. 4257 Factorization
大意: 给定$n,m$, 求长为$n$的序列,乘积等于$m$的方案数.
记$f_{i,x}$为$i$个数乘积$x$的方案, 就有$f_{i,x}=\sum\limits_{ab=x}f_{i-1,a}f_{1,b}$
所以$f$就为积性函数, 有$f_{n,p^k}=\binom{n+k-1}{k}$, 然后相乘即可.
#include <iostream> #include <cstdio> #define REP(i,a,n) for(int i=a;i<=n;++i) #define PER(i,a,n) for(int i=n;i>=a;--i) using namespace std; typedef long long ll; const int P = 1e9+7, INF = 0x3f3f3f3f; ll inv(ll x){return x<=1?1:inv(P%x)*(P-P/x)%P;} const int N = 1e6+10; int n, m, fac[N], ifac[N]; int C(int n, int m) { if (n<m) return 0; return fac[n]*(ll)ifac[m]%P*ifac[n-m]%P; } int main() { fac[0]=1; REP(i,1,N-1) fac[i]=(ll)fac[i-1]*i%P; ifac[N-1]=inv(fac[N-1]); PER(i,0,N-2) ifac[i]=(ll)ifac[i+1]*(i+1)%P; scanf("%d%d", &n, &m); int ans = 1; for (int i=2; i*i<=m; ++i) { int cnt = 0; while (m%i==0) m/=i,++cnt; ans = (ll)ans*C(n+cnt-1,cnt)%P; } if (m>1) ans = (ll)ans*n%P; printf("%d\n", ans); }
11. 3606 Combination Lock
大意:给定字符串$S$, $n$种操作$(L,R)$, 将$s[L...R]$字符加$1$, $z$变为$a$. 每种操作可以执行任意次, 可以按任意顺序执行, 求能否变为回文串.
区间加可以差分为$++c[l],--c[r+1]$, 回文限制相当于所有对称位置的差分之和为$0$.
连边看每个连通块的和是否为$0$即可.
#include <iostream> #include <cstdio> #include <queue> #include <string.h> #define REP(i,a,n) for(int i=a;i<=n;++i) #define pb push_back using namespace std; const int N = 1e6+10; int n, m, sum, c[N], vis[N]; char s[N]; vector<int> g[N]; void add(int x, int y) { g[x].pb(y),g[y].pb(x); } void dfs(int x) { if (vis[x]) return; vis[x] = 1; sum = (sum+c[x])%26; for (int y:g[x]) dfs(y); } int main() { cin>>s+1; m = strlen(s+1); REP(i,1,m+1) { c[i]=(s[i]-s[i-1])%26; add(i,m+2-i); } cin>>n; while (n--) { int l,r; cin>>l>>r; add(l,r+1); } REP(i,1,m+1) { sum = 0; dfs(i); if (sum) return puts("NO"),0; } puts("YES"); }
12. 3605 Zabuton
大意: $n$个人, 初始高度为$0$, 若当前高度不超过$h_i$, 那么第$i$个人可以叠上去, 使高度增加$p_i$, 求最多能叠多少个人.
贪心按$h+p$排序, 然后$dp$一定最优, 考虑证明.
对于两个人$(h_a,p_a),(h_b,p_b)$, 假设$a,b$之前的和为$x$.
若$a$排在$b$前, 有$x\le min(h_a,h_b-p_a)$, 否则有$x\le min(h_b,h_a-p_b)$.
$a$在前比$b$在前更优等价于$min(h_a,h_b-p_a)>min(h_b,h_a-p_b)$
去掉$min$可以得到$h_a+p_a<h_b+p_b$.
#include <iostream> #include <sstream> #include <algorithm> #include <cstdio> #include <math.h> #include <set> #include <map> #include <queue> #include <string> #include <string.h> #include <bitset> #define REP(i,a,n) for(int i=a;i<=n;++i) #define PER(i,a,n) for(int i=n;i>=a;--i) #define hr putchar(10) #define pb push_back #define lc (o<<1) #define rc (lc|1) #define mid ((l+r)>>1) #define ls lc,l,mid #define rs rc,mid+1,r #define x first #define y second #define io std::ios::sync_with_stdio(false) #define endl '\n' #define DB(a) ({REP(__i,1,n) cout<<a[__i]<<' ';hr;}) using namespace std; typedef long long ll; typedef pair<int,int> pii; const int P = 1e9+7, INF = 0x3f3f3f3f; ll gcd(ll a,ll b) {return b?gcd(b,a%b):a;} ll qpow(ll a,ll n) {ll r=1%P;for (a%=P;n;a=a*a%P,n>>=1)if(n&1)r=r*a%P;return r;} ll inv(ll x){return x<=1?1:inv(P%x)*(P-P/x)%P;} inline int rd() {int x=0;char p=getchar();while(p<'0'||p>'9')p=getchar();while(p>='0'&&p<='9')x=x*10+p-'0',p=getchar();return x;} //head const int N = 5e3+10; int n; ll dp[N]; struct _ { int x,y; bool operator < (const _ &rhs) const { return x+y<rhs.x+rhs.y; } } a[N]; int main() { cin>>n; REP(i,1,n) cin>>a[i].x>>a[i].y; sort(a+1,1+a+n); memset(dp,0x3f,sizeof dp); dp[0] = 0; REP(i,1,n) { PER(j,0,i-1) if (dp[j]<=a[i].x) { dp[j+1]=min(dp[j+1],dp[j]+a[i].y); } } PER(i,0,n) if (dp[i]<=1e16) return printf("%d\n",i),0; }
13. 2292 Division into Two
大意: 给定序列, 求划分为两个集合$X,Y$, 满足$X$中任意两数之差的绝对值不少于$A$, $Y$中任意两数之差的绝对值不少于$B$, 求方案数.
$DP$好题.
首先$O(n^2)$的$DP$很容易想, 只要枚举上次出现位置即可.
#include <iostream> #include <cstdio> #define REP(i,a,n) for(int i=a;i<=n;++i) using namespace std; typedef long long ll; const int P = 1e9+7, INF = 0x3f3f3f3f; const int N = 1e3+10; int n, dp[2][2][N]; ll a,b,s[N]; void add(int &x, int y) {x+=y;if (x>=P)x-=P;} int main() { cin>>n>>a>>b; REP(i,1,n) cin>>s[i]; s[0] = -1e18; int cur = 0, ans = 0; dp[0][0][0] = 1; //dp[i][z][j] //z为0, X上个数位置在i, Y上个数位置在j //z为1, X上个数位置在j, Y上个数位置在i REP(i,1,n) { cur ^= 1; memset(dp[cur],0,sizeof dp[cur]); REP(j,0,i-1) REP(z,0,1) { int &r = dp[!cur][z][j]; if (!r) continue; if (!z&&s[i]-s[i-1]>=a||z&&s[i]-s[i-1]>=b) { add(dp[cur][z][j],r); if (i==n) add(ans,r); } if (!z&&s[i]-s[j]>=b||z&&s[i]-s[j]>=a) { add(dp[cur][!z][i-1],r); if (i==n) add(ans,r); } } } printf("%d\n", ans); }
考虑$O(n)$的做法, 记$dp_i$为集合$Y$取第$i$个数的方案数, 可转移的$j$要满足
$$s_i-s_j\ge B$$
$$s_k-s_{k-1}\ge A,k\in [j+2,i-1]$$
所以$j$是一段连续的区间, 可以前缀和优化一下即可$O(n)$
#include <iostream> #include <cstdio> #define REP(i,a,n) for(int i=a;i<=n;++i) using namespace std; typedef long long ll; const int P = 1e9+7; const int N = 1e6+10; int n,f[N]; ll s[N],a,b; int main() { cin>>n>>a>>b; if (a>b) swap(a,b); REP(i,1,n) cin>>s[i]; REP(i,3,n) if (s[i]-s[i-2]<a) return puts("0"),0; f[0] = 1; int l=0, r=0; s[n+1] = 2e18; REP(i,1,n+1) { while (r<i-1&&s[i]-s[r+1]>=b) ++r; if (l<=r) f[i]=(f[r]-(l?f[l-1]:0))%P; f[i] = (f[i]+f[i-1])%P; if (i>1&&s[i]-s[i-1]<a) l=i-1; } int ans = (f[n+1]-f[n])%P; if (ans<0) ans+=P; printf("%d\n",ans); }