\(\cal T_1\) \(01\) 串
\(\mathbb{Description}\)
给一个长度为 \(n\) 的由 01?
构成的串 \(S\). 问有多少个 01
串的序列 \(t_1,t_2,\dots,t_n\) 满足:
- 第 \(i\) 个串的长度为 \(i\);
- 对于所有的 \(i(2\le i\le n)\),\(t_{i-1}\) 是 \(t_i\) 的子序列;
- \(t_n\) 可以由 \(S\) 将每个
?
替换成0
或1
得到。
输出序列个数对 \(998244353\) 取模的答案。
\(n\le 250000\).
\(\mathbb{Solution}\)
一些闲话:这种转化真的是人能够想到的吗……
先不考虑包含 ?
的情况,首先可以考虑从空字符串构造出我们想要的 01
序列,然后从 01
序列删除字符以达到空字符串:
- 在开头插⼊⼀个
1
; - 把⼀个
0
换成01
; - 把⼀个
1
换成01
; - 在末尾插⼊⼀个
0
。
这样就能保证不重不漏,可以看成它们在一组线性基里。接着考虑逆操作:假设在开头补⼀个 0
,结尾补⼀个 1
(之后的末尾插入 0
实际上是在末尾 1
之前插入),那么就可以看成每次可以选择⼀个 01
删除其中的⼀个 0
或者 1
。更加玄幻的操作来了:在每两个字符之间插⼊分隔符。⽐如 100 -> 0F1T0C0Y1
,每次选择 01
删除的时候同时也把分隔符删除。那么每个分隔符被删除的时间相当于⼀个大小为 \(n+1\) 的排列,如果 \(s_i\) 元素是 0
,那么右边的分隔符会先删除,否则左边的分隔符先删除。也就是说,元素 0
相当于 >
,表示右边的 \(t\) 小于左边的 \(t\)。问题就转化成 \(\text{LOJ - 575 }\)不等关系,对于 ?
,显然对左右的偏序关系没有影响,所以用 ?
将序列分成若干段,再将答案相乘即可。
但是做这道题之前我还不会不等关系,所以下面就是不等关系的题解,并且因为太懒,下面的代码也是不等关系。
由于给定的是一个排列,所以 "不满足 >
" 就等价于 "满足 <
",而只满足一种偏序关系的序列是非常好算的,假设序列长度为 \(n\),分成了 \(m\) 个只由 <
偏序组成的序列,每个序列长度为 \(l_i\),序列连接部分可以乱选,方案数就是 \(\frac{(n+1)!}{\prod (l_i+1)!}\)。我们可以考虑容斥:令 \(f(k)\) 为钦定 \(k\) 个大于号不满足的答案,\(g(k)\) 为恰好 \(k\) 个大于号不满足的答案。"至少" 好像一般对应着 "不满足" 呢。
由 "至少式" \(f(n)=\sum_{i=n}^m \binom{i}{n}g(i)\Leftrightarrow g(n)=\sum_{i=n}^m (-1)^{i- n}\binom{i}{n}f(i)\),我们得到 \(g(0)=\sum_{i=0}^n (-1)^i\cdot f(i)\).
于是 \(\mathtt{dp}\) 一下函数 \(f\),过程中乘上 \(-1\) 的系数即可得到 \(g(0)\),转移方程如下
解释一下,\(i\) 必须满足 \(s_i\) 为大于号,\(dp_i\) 就是 \(i\) 可以乱选,转移就是枚举上一个可以乱选的 \(j\),那么 \((j,i)\) 之间的大于号都被钦定成了小于号。\(-1\) 就是累计被钦定的 >
数量,而 \(\frac{1}{(i-j)!}\) 就是上面方案数的分母。所以最终答案还需要乘上 \((n+1)!\). 而 \(t\) 数组是 >
的前缀和。
这样暴力算是过不去的,我们可以继续化简这个柿子
可以发现这个可以卷积,写一个分治 \(\rm ntt\) 即可。注意还要新建 \(n+1\) 号位来统计答案。
\(\mathbb{Code}\)
我真它吗调哭了,早知道不求写了,焯!
# include <cstdio>
# include <cctype>
# define print(x,y) write(x), putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while(!isdigit(s=getchar())) f|=(s=='-');
for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
return f? -x : x;
}
template <class T>
inline void write(T x) {
static int writ[50], w_tp=0;
if(x<0) putchar('-'), x=-x;
do writ[++w_tp]=x-x/10*10, x/=10; while(x);
while(putchar(writ[w_tp--]^48), w_tp);
}
# include <cstring>
# include <iostream>
using namespace std;
const int maxn = 250005;
const int SIZE = maxn<<1;
const int mod = 998244353, G = 3;
inline int dec(int x,int y) { return x-y<0?x-y+mod:x-y; }
inline int inc(int x,int y) { return x+y>=mod?x+y-mod:x+y; }
inline int inv(int x,int y=mod-2) {
int r=1;
for(; y; y>>=1, x=1ll*x*x%mod)
if(y&1) r=1ll*r*x%mod;
return r;
}
char s[maxn];
int t[maxn],rev[SIZE],v[SIZE];
int n,g[SIZE],f[SIZE],W[2][19],h[SIZE];
inline int sgn(int x) { return (x&1)?mod-1:1; }
void ntt(int* f,int lim,bool opt=0) {
for(int i=0;i<lim;++i)
if(i<rev[i]) swap(f[i],f[rev[i]]);
for(int mid=1, lg=1; mid<lim; mid<<=1, ++lg) {
for(int i=0; i<lim; i+=(mid<<1)) {
int w = 1;
for(int j=0; j<mid; ++j, w=1ll*w*W[opt][lg]%mod) {
int tmp = 1ll*w*f[i|j|mid]%mod;
f[i|j|mid] = dec(f[i|j],tmp);
f[i|j] = inc(f[i|j],tmp);
}
}
}
}
void dicon(int l,int r,int d) {
if(d<=0 || l>=n) return;
int mid = l+r>>1; dicon(l,mid,d-1);
for(int i=mid;i<r;++i) h[i-l] = 0;
for(int i=l;i<mid;++i) h[i-l] = 1ll*f[i]*sgn(t[i])%mod;
v[0]=0; // very important!!!
for(int i=0;i<r-l;++i)
v[i+1] = g[i+1], rev[i] = (rev[i>>1]>>1)|((i&1)<<d-1);
ntt(h,r-l), ntt(v,r-l);
int Inv = inv(r-l);
for(int i=0;i<r-l;++i) h[i] = 1ll*h[i]*v[i]%mod*Inv%mod;
ntt(h,r-l,1);
for(int i=mid;i<r;++i) if(s[i]=='>')
f[i] = inc(f[i],1ll*sgn(t[i-1])*h[i-l]%mod);
dicon(mid,r,d-1);
}
void preWork() {
W[1][18] = inv(W[0][18]=inv(G,3808));
for(int j=0;j<2;++j) for(int i=17;~i;--i)
W[j][i] = 1ll*W[j][i+1]*W[j][i+1]%mod;
}
int main() {
scanf("%s",s+1); n=strlen(s+1); preWork();
int lim=1, bit=0, tmp=1; f[0]=1;
for(int i=1;i<=n+1;++i) t[i] = t[i-1]+(s[i]=='>');
s[n+1] = '>';
for(int i=1;i<=n+1;++i) tmp=1ll*tmp*i%mod;
g[n+1] = inv(tmp);
for(int i=n;i>=0;--i) g[i] = 1ll*g[i+1]*(i+1)%mod;
n += 2;
while(lim<n) lim<<=1, ++bit;
dicon(0,lim,bit);
print(1ll*tmp*f[n-1]%mod,'\n');
return 0;
}
\(\cal T_2\) 做菜
\(\mathbb{Description}\)
你是一位米其林轮胎大厨。
今天你有 \(n\) 个顾客,每个人会点一道菜,做这道菜需要花费 \(a_i\) 的时间,这位顾客吃这道菜需要花费 \(b_i\) 的时间。因为你是一个做事精益求精的人,所以你只能在同一个时刻做一道菜。对于每位顾客,一旦你上菜他就开始吃,吃完之后就会离开。现在,你想选择服务其中 \(k\) 位顾客,使得你的劳动时间最短。这里的劳动时间指从做第一道菜开始到最后一位顾客吃完离开。
给定 \(\{b\},k\),对于所有每个数在 \(1\) 到 \(V\) 之间的数列 \(\{a\}\),求出最短劳动时间,答案就是它们的总和对 \(998244353\) 取模。你需要回答 \(q\) 组询问,每组询问的 \(\{b\}\) 和 \(V\) 都是相同的,只有 \(k\) 可能会不同。
\(1\le n\le 30,1\le V\le 20,1\le b_i\le 600\).
\(\mathbb{Solution}\)
\(\text{Subtask 1}\):\(n\le 9,V\le 3\)
数据范围那么小,直接穷举开草。首先想到枚举 \(\{a\}\) 也就是 \(\mathcal O(V^n)=\mathcal O(19683)\),问题就是这个序列的最短劳动时间怎么算,显然再枚举 \(9\) 的全排列是不可接受的,所以我们要找性质。这看上去多像序列贪心啊,可以想到对于二元组 \((p_i,p_{i+1})\),如果能证明某种排序之后,存在
恒成立,那么这个排序就是可行的。这个柿子显然可以瞎 \(\rm jb\) 分类讨论(还是写一下,以下暂记 \(v_i=a_{p_i}+b_{p_i}\)):
- \(v_i+a_{p_{i+1}}>v_{i+1}\and v_i<v_{i+1}+a_{p_{i}}\):在这种情况下,要想满足上面的条件化简出来的柿子是 \(b_{p_{i}}>b_{p_{i+1}}\),所以我们不妨先这样排序;
- \(v_i+a_{p_{i+1}}<v_{i+1}\and v_i>v_{i+1}+a_{p_i}\):代入上文的排序方式也满足条件;
- \(v_i+a_{p_{i+1}}>v_{i+1}\and v_i>v_{i+1}+a_{p_i}\):这种情况难道不是铁了 \(p_i\) 在前不优吗?别急,代入排序方式发现这种情况是不存在的;
- \(v_i+a_{p_{i+1}}<v_{i+1}\and v_i<v_{i+1}+a_{p_i}\):这个就不用说了。
综上,我们归纳出最优排序方式应当是 \(\{b\}\) 从大到小排序。所以对于每一种 \(\{a\}\),我们 \(\mathcal O(2^n)\) 枚举选择的顾客集合,算出答案累加到对应的 \(\text{ans}(k)\) 上即可。
\(\text{Subtask 2}\):\(k\le 2\)
又到了最喜欢的分类讨论时间!但是我的 \(\mathtt{dp}\) 真的太烂了!焯!
对于 \(k=1\),直接的想法是枚举最小值点(令 "权值" 为 \(a_i+b_i\)),然后组合计数其它的位置,但是真的很难算,所以考虑用 \(\mathtt{dp}\) 来帮助我们计算。钦定最小值(在 \(10^3\) 左右)\(i\),我们考虑枚举序列中第一个权值等于 \(i\) 的点 \(j\),可以用 \(\mathtt{dp}\) 算出在 \(i\) 之前(和之后)不合法的方案数,乘起来即可。或者也可以利用容斥,枚举序列中第一个权值小于等于 \(i\) 的点 \(j\),这样后面的数字可以 \((n-j)^V\) 直接算,算出来就是最小值点至少为 \(i\) 的方案数。
对于 \(k=2\),我们直接应用上文的结论,将 \(\{b\}\) 进行排序,这样对于二元组 \((i,j)(i<j)\),它们的最短劳动时间就是 \(\min\{a_i+b_i,a_i+a_j+b_j\}\). 还是枚举 \(i\),令 \(\text{ban}_{i,j}\) 为前 \(i\) 个数,若 \(j=0\) 则表示没有任何一位顾客能够成为第一位顾客的方案数,若 \(j>0\),则表示 至少 存在一位顾客成为第一位顾客,且这些满足条件的顾客之中,\(a\) 值最小值为 \(j\) 的方案数,考虑 \(a\) 值最小值是因为之后第二位顾客一定会选 \(a\) 值最小的。
\(\mathbb{Code}\)
# include <cstdio>
# include <cctype>
# define print(x,y) write(x), putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while(!isdigit(s=getchar())) f|=(s=='-');
for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
return f? -x: x;
}
template <class T>
inline void write(T x) {
static int writ[50], w_tp=0;
if(x<0) putchar('-'), x=-x;
do writ[++w_tp]=x-x/10*10, x/=10; while(x);
while(putchar(writ[w_tp--]^48), w_tp);
}
# include <iostream>
# include <algorithm>
using namespace std;
const int mod = 998244353;
inline int Inc(int x,int y) { return x+y>=mod?x+y-mod:x+y; }
inline void inc(int& x,int y) { x = (x+y>=mod?x+y-mod:x+y); }
int n,V,b[40];
namespace FTCY {
int a[40],ans[40],mn[40];
void calc() {
int lim = (1<<n), mx, cur, cnt;
for(int i=1;i<=n;++i) mn[i]=1e9;
for(int s=0;s<lim;++s) {
mx=cur=0;
for(int i=1;i<=n;++i) if(s>>i-1&1)
mx = max(mx,cur+a[i]+b[i]), cur+=a[i];
cnt = __builtin_popcount(s);
mn[cnt] = min(mn[cnt],mx);
}
for(int i=1;i<=n;++i) inc(ans[i],mn[i]);
}
void constr(int x) {
if(x==n+1) return calc(), void();
for(int i=1;i<=V;++i) a[x]=i, constr(x+1);
}
void ttl() {
sort(b+1,b+n+1,[](int x,int y) { return x>y; });
constr(1);
for(int q=read(9); q; --q) print(ans[read(9)],'\n');
}
}
namespace xyy {
int pw[40],ban[40][1300],f[1300],fk[40];
void cnm(int& ans) {
int minb = 600;
for(int i=1;i<=n;++i) minb=min(minb,b[i]);
/* 容斥写法~
for(int i=1; i<=minb+V; ++i) {
ban[0][0]=1;
for(int j=1;j<=n;++j) ban[j][0]=0;
for(int j=1;j<=n;++j) for(int v=1;v<=V;++v)
if(b[j]+v<=i) inc(f[i],1ll*ban[j-1][0]*pw[n-j]%mod);
else inc(ban[j][0],ban[j-1][0]);
inc(ans,1ll*i*Inc(f[i],mod-f[i-1])%mod);
}
*/
for(int i=1; i<=minb+V; ++i) {
ban[0][0]=1; fk[n+1]=1;
for(int j=1;j<=n;++j) ban[j][0]=0, fk[j]=0;
for(int j=n;j>=1;--j) for(int v=1;v<=V;++v)
if(b[j]+v>=i) inc(fk[j],fk[j+1]);
for(int j=1;j<=n;++j) for(int v=1;v<=V;++v)
if(b[j]+v==i) inc(f[i],1ll*ban[j-1][0]*fk[j+1]%mod);
else inc(ban[j][0],ban[j-1][0]);
inc(ans,1ll*i*f[i]%mod);
}
}
void rnm(int& ans) {
int minb = 601, minb2;
for(int i=1;i<=n;++i)
if(b[i]<=minb) minb2=minb, minb=b[i];
else minb2 = min(minb2,b[i]);
for(int i=1; i<=minb2+V+V; ++i) {
ban[0][0]=1, f[i]=0;
for(int j=1;j<=n;++j) {
for(int k=0;k<=i;++k) ban[j][k]=0;
for(int v=1;v<=V;++v) {
for(int k=1;k<=i;++k) {
if(k+b[j]+v<=i) inc(f[i],1ll*ban[j-1][k]*pw[n-j]%mod);
else if(b[j]+v<=i) inc(ban[j][min(k,v)],ban[j-1][k]);
else inc(ban[j][k],ban[j-1][k]);
}
if(b[j]+v<=i) inc(ban[j][v],ban[j-1][0]);
else inc(ban[j][0],ban[j-1][0]);
}
}
inc(ans,1ll*Inc(f[i],mod-f[i-1])*i%mod);
}
}
void become_a_werewolf_for_you() {
pw[0]=1; int ans[3]={};
for(int i=1;i<=n;++i) pw[i] = 1ll*pw[i-1]*V%mod;
sort(b+1,b+n+1,[](int x,int y) { return x>y; });
cnm(ans[1]); rnm(ans[2]);
for(int q=read(9); q; --q) print(ans[read(9)],'\n');
}
}
int main() {
freopen("b.in","r",stdin);
freopen("b.out","w",stdout);
n=read(9), V=read(9);
for(int i=1;i<=n;++i) b[i]=read(9);
if(n<=9 && V<=3) return FTCY::ttl(), (0-0);
xyy::become_a_werewolf_for_you();
return 0;
}
\(\cal T_3\) 树上路径
\(\mathbb{Description}\)
有一棵 \(n\) 个点的树,每条边有一个边权(可能为负)。要求选择若干条简单路径,满足:
- 简单路径恰好包含 \(k\) 条边;
- 每条边在至多一条简单路径里(每个点可以在多个简单路径里)。
输出所有选择方案中边权和最大的方案的边权和。
\(n\le 2\cdot 10^5,3\le k\le 4,-10^9\le w\le 10^9\).
\(\mathbb{Solution}\)
由于 \(k\) 只可能是 \(3,4\),所以可以进行分类讨论,不妨设 \(dp_{i,j}\) 为在子树 \(i\) 中,点 \(i\) 向下延伸出长度为 \(j\) 的链的最大权值和。
- 对于 \(k=3\):由于儿子向下延伸的链可以在点 \(i\) 匹配,合法的情况一定是点 \(i\) 若干条 \(1,2\) 链相互匹配(个数相等),至多再多出一条 \(1,2\) 链和上面的节点匹配;
- 对于 \(k=4\):有 "\(2\) 的个数为偶数,\(1,3\) 个数相等"、"\(2\) 的个数为偶数,\(1,3\) 个数相差 \(1\)" 和 "\(2\) 的个数为奇数,\(1,3\) 个数相等"。
但我们注意到,此时 \(\mathtt{dp}\) 复杂度是 \(\mathcal O(n^2)\) 的,不可接受,所以就需要一个结论:对于长度为 \(n\) 的只包含 \(1,-1\) 的 随机 序列,它的前缀和是 \(\mathcal O(\sqrt n)\) 级别的。所以我们将节点 \(i\) 的儿子全都打乱再进行 \(\mathtt{dp}\),"个数相等" 限制那一维就可以缩减到 \(\sqrt n\) 级别,复杂度降低到 \(\mathcal O(n\sqrt n)\).
一些闲话:你可以将 "个数相等" 限制那一维的加一/减一变化看成 \(1,-1\),事实上,还有可能是 \(0\)(也就是更新失败)。还有就是,出题人真的用脚造数据,我先开始忘记加 shuffle()
照样跑得飞快😂
\(\mathbb{Code}\)
代码其实基本上是嫖的。
# include <cstdio>
# include <cctype>
# define print(x,y) write(x), putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while(!isdigit(s=getchar())) f|=(s=='-');
for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
return f? -x: x;
}
template <class T>
inline void write(T x) {
static int writ[50], w_tp=0;
if(x<0) putchar('-'), x=-x;
do writ[++w_tp]=x-x/10*10,x/=10; while(x);
while(putchar(writ[w_tp--]^48), w_tp);
}
# include <vector>
# include <random>
# include <iostream>
using namespace std;
typedef long long i64;
typedef pair <int,int> par;
const int maxn = 2e5+5;
const i64 infty = 1e15;
int n,k;
i64 dp[maxn][4],f[3000][2];
vector <par> e[maxn];
void dfs(int u,int fa) {
for(const auto& to:e[u]) if(to.first^fa) dfs(to.first,u);
int all = int(e[u].size())-(u!=1);
int lim = min(2001,all<<1|1), haf = lim>>1;
for(int i=0;i<=lim+5;++i) f[i][0]=f[i][1]=-infty;
f[haf][0]=0;
i64 cur[2], lst[2];
for(const auto& to:e[u]) if(to.first^fa) {
int v=to.first, w=to.second;
lst[0]=-infty, lst[1]=-infty;
for(int i=0;i<=lim;++i) {
cur[0]=f[i][0], cur[1]=f[i][1];
for(int j=0;j<2;++j) {
f[i][j] += max(dp[v][0],dp[v][k-1]+w);
f[i][j] = max(f[i][j],lst[j]+dp[v][0]+w);
f[i][j] = max(f[i][j],f[i+1][j]+dp[v][k-2]+w);
if(k==4) f[i][j] = max(f[i][j],cur[j^1]+dp[v][1]+w);
}
lst[0]=cur[0], lst[1]=cur[1];
}
}
dp[u][0] = f[haf][0];
dp[u][1] = f[haf+1][0];
dp[u][k-1] = (haf? f[haf-1][0]: -infty);
if(k==4) dp[u][2] = f[haf][1];
}
int main() {
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
n=read(9), k=read(9);
for(int i=1;i<n;++i) {
int u=read(9), v=read(9), w=read(9);
e[u].emplace_back(make_pair(v,w));
e[v].emplace_back(make_pair(u,w));
}
dfs(1,0);
print(dp[1][0],'\n');
return 0;
}