\(\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\) 将每个 ? 替换成 01 得到。

输出序列个数对 \(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)\),转移方程如下

\[dp_i=\sum_{j=0}^{i-1}dp_j\cdot (-1)^{t_{i-1}-t_j}\cdot \frac{1}{(i-j)!} \]

解释一下,\(i\) 必须满足 \(s_i\) 为大于号,\(dp_i\) 就是 \(i\) 可以乱选,转移就是枚举上一个可以乱选的 \(j\),那么 \((j,i)\) 之间的大于号都被钦定成了小于号。\(-1\) 就是累计被钦定的 > 数量,而 \(\frac{1}{(i-j)!}\) 就是上面方案数的分母。所以最终答案还需要乘上 \((n+1)!\). 而 \(t\) 数组是 > 的前缀和。

这样暴力算是过不去的,我们可以继续化简这个柿子

\[dp_i=(-1)^{t_{i-1}}\sum_{j=0}^{i-1}dp_j\cdot (-1)^{-t_j}\cdot \frac{1}{(i-j)!} \]

可以发现这个可以卷积,写一个分治 \(\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})\),如果能证明某种排序之后,存在

\[\max\{a_{p_i}+b_{p_i},a_{p_{i+1}}+b_{p_{i+1}}+a_{p_{i}}\}\le \max\{a_{p_{i}}+b_{p_{i}}+a_{p_{i+1}},a_{p_{i+1}}+b_{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;
}
posted on 2020-03-25 08:41  Oxide  阅读(27)  评论(0编辑  收藏  举报