动态规划题单(持续更新)

前言:

这是笔者保证做过的且我自己认为很不错的 d p dp dp题集,难度对应 c o d e f o r c e s codeforces codeforces 1700 − 2500 1700-2500 17002500不等。(点击题目有链接

2023湖北省赛:众数(数位dp)

题意:

定义 f [ x ] f[x] f[x] x x x在十进制下出现次数最多的数的出现次数, f [ 2223 ] = 3 , f [ 2233 ] = 2 f[2223]=3,f[2233]=2 f[2223]=3,f[2233]=2
给定 L , R L,R L,R,求 ∑ i = L R f [ i ] \sum_{i=L}^{R}{f[i]} i=LRf[i]

思路:

首先我们定义 p r e [ i ] pre[i] pre[i]为当前枚举到的数位中i出现的次数,同时定义主元素为出现最多的数字,假设主元素为 x x x,那么对于非主元素 y y y,若 y < x y<x y<x,则 p r e [ y ] < = p r e [ x ] pre[y]<=pre[x] pre[y]<=pre[x],若 y > x y>x y>x,则 p r e [ y ] > p r e [ x ] pre[y]>pre[x] pre[y]>pre[x]。这样定义是为了防止重复计算,比如说主元素为3时 f [ 2233 ] = 2 f[2233]=2 f[2233]=2,主元素为2时, f [ 2233 ] = 0 f[2233]=0 f[2233]=0
那么我们数位dp过程中,我们去枚举主元素以及主元素的出现次数,假设后面还有 p o s pos pos个位置没有填数,那么我们可以确定主元素最少还需在这 p o s pos pos个位置放几个,紧接着我们根据这个又可以确定其它非主元素最多可以放几个,这个我们跑个背包就可以求得,但是这样还是会T。
假如现在我们确定主元素为3后求得0最多放1个,1最多放3个,2最多放2个,确定主元素为4后求得1最多放1个,2最多放3个,3最多放2个。这两种方案其实是一样的,换句话说,我们并不需要关心主元素非主元素是谁,我们只需要知道个数就可以,那么这一部分我们可以用 H a s h Hash Hash存储。
复杂度不是很懂,但是跑的飞快。

代码

int d[20], idx;
LL C[20][20];
map<int, LL> dp[20][20];
map<int, array<LL, 20>> mp[20][20];
// cnt = sum(pre)
LL get_hash(array<int, 10> pre) {
    LL res = 0;
    for(int i = 0; i < 10; i++)
        res = (res << 4) + (res << 2) + pre[i];
    return res;
}
// 主元素为val,待填位置pos个,pre为已经填的位置的状态
array<LL, 20> cal(int val, int pos, array<int, 10> pre) {
    array<LL, 20> ans{};
    ans[0] = -1;
    int m = 0; // 主元素在剩下的pos个最少要放几个
    for(int i = 0; i < 10; i++)
        if(i != val) m = max(m, pre[i] - pre[val] + (i > val));
    if(m > pos) return ans;
    ans[0] = 0;
    array<int, 10> _pre{}; // 主元素为val时,i最多放_pre[i]个
    for(int i = 0; i < 10; i++) 
        if(i != val) 
            _pre[i] = min(pre[val] + m - pre[i] - (i > val), pos - m);
    _pre[val] = 20;
    sort(all(_pre));
    LL hash = get_hash(_pre);
    if(mp[pos][m].find(hash) != mp[pos][m].end())
        return mp[pos][m][hash];
    LL f[20], g[20];
    //枚举主元素放k个
    for(int k = m; k <= pos; k++) {
        memset(g, 0, sizeof g);
        g[k] = C[pos][k];
        for(int i = 0; i < 9; i++)
            if(_pre[i]) {
                memset(f, 0, sizeof f);
                for(int j = 0; j <= pos; j++)
                    for(int x = 0; g[j] && x <= _pre[i] && x + j <= pos; x++)
                        f[x + j] += g[j] * C[pos - j][x];
                swap(f, g);
            }
        ans[k] = g[pos];
        for(int i = 0; i < 9; i++) _pre[i] = min(_pre[i] + 1, pos - k - 1);
    }
    return mp[pos][m][hash] = ans;
}
//cnt表示已经确定几个数,也就是pre[i]的和
LL dfs(int pos, array<int, 10> pre, bool lim, bool lead, int cnt) {
    if(pos < 0) {
        int mx = 0;
        for(int i = 0; i < 10; i++) 
            mx = max(mx, pre[i]);
        return mx;
    }
    LL _hash = get_hash(pre);
    if(!lim && dp[pos][lead].find(_hash) != dp[pos][lead].end())
        return dp[pos][lead][_hash]; 
    LL res = 0;
    for(int i = 0, high = lim ? d[pos] : 9; i <= high; i++) {
        if(lead && i == 0 && pos) {
            res += dfs(pos - 1, pre, lim && (i == high), lead, cnt);
        } else {
            array<int, 10> now(pre);
            now[i]++;
            if(pos == 0 || (lim && (i == high)))
                res += dfs(pos - 1, now, lim && (i == high), lead && (i == 0), cnt + 1);
            else {
                //后面的数可以随便填,跑背包
                for(int k = 0; k < 10; k++) {
                    auto ans = cal(k, pos, now);
                    if(ans[0] < 0) continue;
                    // ans[0] < 0代表k不能成为主元素
                    // 剩余位置中放i个主元素的方案数为ans[i]
                    for(int j = 0; j <= pos; j++) 
                        res += (now[k] + j) * ans[j];
                }
            }
        }
    }
    if(!lim) dp[pos][lead][_hash] = res;
    return res;
}
LL solve(LL x) {
    if(x < 0) return 0;
    if(x <= 10) return x + 1;
    LL res = 0;
    bool lim = 1;
    if(x == 1e18) {
        x--;
        res = 18;
        lim = 0;
    }
    idx = -1;
    while(x) d[++idx] = x % 10, x /= 10;
    array<int, 10> pre{}; // i目前出现了pre[i]次
    return res + dfs(idx, pre, lim, 1, 0);
}

ABC310 F- Make 10 Again

题意:

给你 n n n个骰子,每个骰子的面值为 1 − a [ i ] 1-a[i] 1a[i]。骰完 n n n个骰子后,若从中选择一些骰子使得面值和为10,那么这是一个合格的方案,求所有合格的方案的概率。
比如当前4个骰子分别为1,2,3, 7。那么我们可以选3,4,或者选1,2,4,都是合理的,但是这只能算一种方案。

思路:

定义 f [ i ] [ j ] f[i][j] f[i][j]已经投前 i i i个骰子,能合成的[0,10]中的数的集合的状态是 j j j的概率。
我们发现,这样定义我们并不会重复计算,因为我们只关心这个集合的状态,而不关心某个数选或不选。

代码:

    int n;
    cin >> n;
    int m = (1 << 11) - 1;
    vector<vector<mint>> f(n + 1, vector<mint> (m + 1, 0));
    f[0][1] = 1;
    for(int i = 1; i <= n; i++) {
        int x; cin >> x;
        int flag = x > 10 ? x : 0;
        mint p = ksm(x, mod - 2);
        x = min(x, 10);
        for(int j = 1; j <= m; j++) {
            for(int k = 1; k <= x; k++) {
                int state = j | (1 << k);
                for(int y = 0; y <= 10; y++)
                    if((j >> y & 1) && y + k <= 10)
                        state |= 1 << (y + k);
                f[i][state] += f[i - 1][j] * p;
            }
            if(flag) f[i][j] += f[i - 1][j] * (flag - 10) * p;
        }
    }
    mint res = 0;
    for(int i = 1; i <= m; i++)
        if(i >> 10 & 1) res += f[n][i];
    for(int i = 1; i <= m; i++)
        if(f[n][i] != 0) cout << i << '\n';
    cout << res << '\n';

2023杭电多校第二场1010.Klee likes making friends

题意:

给定一个长度为 n , n ≤ 20000 n,n\leq 20000 n,n20000数组 a a a,你要选择一些位置并花费代价 a [ i ] a[i] a[i],保证每连续 m , m ≤ 2000 m, m\leq 2000 m,m2000个位置至少有两个位置被选。

思路:

我们只需关心当前最后选的两个位置即可,因此我们定义 d p [ i ] [ j ] dp[i][j] dp[i][j]为最后一个位置为 i i i,倒数第二个位置为 j j j,但是空间显然不允许,但是因为倒数第二个元素距离最后一个肯定不超过 m m m,我们重新定义 d p [ i ] [ j ] dp[i][j] dp[i][j]为最后一个位置为 i i i,倒数第二个元素距 i i i的距离为 j j j。那么我们考虑倒数第三个元素的位置,显然只能在区间 [ i − m , k − 1 ] , k 为倒数第二个元素的位置 [i-m,k-1],k为倒数第二个元素的位置 [im,k1],k为倒数第二个元素的位置,所以我们要维护一个后缀最小值,观察到这三个位置的差不超过 m m m,因为第一维可以继续优化,最终只需要 m ∗ m m*m mm的空间。

代码:

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    int T;
    cin >> T;
    while(T--) {
        int n, m;
        cin >> n >> m;
        vector<int> a(n + 1), pos(n + 1);
        for(int i = 1; i <= n; i++) {
            cin >> a[i];
            pos[i] = i % m;
        } 
        vector<vector<int>> dp(m + 1, vector<int> (m + 1, inf));
        vector<vector<int>> mn(m + 1, vector<int> (m + 1, inf));
        for(int i = 1; i <= m; i++) {
            for(int j = i - 1; j >= 1; j--) {
                dp[pos[i]][i - j] = a[i] + a[j];
                mn[pos[i]][i - j] = min(mn[pos[i]][i - j - 1], dp[pos[i]][i - j]);
            }
        }
        for(int i = m + 1; i <= n; i++) {
            for(int j = i - 1; j >= i - m + 1; j--) {
                dp[pos[i]][i - j] = a[i] + mn[pos[j]][j - (i - m)];
                mn[pos[i]][i - j] = min(mn[pos[i]][i - j - 1], dp[pos[i]][i - j]);
            }
        }
        int ans = inf;
        for(int i = n - m + 1; i <= n; i++) {
            for(int j = i - 1; j >= n - m + 1; j--)
                ans = min(ans, dp[pos[i]][i - j]);
        }
        cout << ans << '\n';
    }
    
    return 0;
}

ARC157C

题意:

给定一个只包含 X Y XY XY两种字符的 n ∗ m n*m nm的矩阵,每次只能往右、下走,这样走到 ( n , m ) (n,m) (n,m)会得到一个字符串,定义一个字符串的权值为连续两个字符是 Y Y Y的个数的平方,比如 Y Y Y = 2 2 = 4 YYY=2^2=4 YYY=22=4。求所有到达 ( n , m ) (n,m) (n,m)所形成的字符串的权值和。

思路:

好像很套路?
我们定义:
f 1 [ i ] [ j ] f1[i][j] f1[i][j]为到 ( i , j ) (i,j) (i,j)的路径数量
f 2 [ i ] [ j ] f2[i][j] f2[i][j]为到 ( i , j ) (i,j) (i,j)的所有路径的 Y Y YY YY对的数量
f 3 [ i ] [ j ] f3[i][j] f3[i][j]为到 ( i , j ) (i,j) (i,j)的所有串的权值和。
转移比较朴素。

mint f1[M][M]; // 走到[i,j]的路径数
mint f2[M][M], f3[M][M];
 
int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
    cout.tie(nullptr);
 
    int n, m;
    cin >> n >> m;
    vector<string> a(n + 1);
    for(int i = 1; i <= n; i++) {
        cin >> a[i];
        a[i] = " " + a[i];
    }
    for(int i = 1; i <= n; i++) {
        f1[i][1] = 1;
        f2[i][1] = f2[i - 1][1] + (a[i][1] == 'Y' && a[i - 1][1] == 'Y');
        f3[i][1] = f2[i][1] * f2[i][1];
        // f3[i][1] = f3[i - 1][1] + .
    }
    for(int i = 1; i <= m; i++) {
        f1[1][i] = 1;
        f2[1][i] = f2[1][i - 1] + (a[1][i] == 'Y' && a[1][i - 1] == 'Y');
        f3[1][i] = f2[1][i] * f2[1][i];
    }
    for(int i = 2; i <= n; i++)
        for(int j = 2; j <= m; j++) {
            f1[i][j] = f1[i - 1][j] + f1[i][j - 1];
            f2[i][j] = f2[i - 1][j] + f2[i][j - 1];
            if(a[i - 1][j] == 'Y' && a[i][j] == 'Y') 
                f2[i][j] += f1[i - 1][j];
            if(a[i][j - 1] == 'Y' && a[i][j] == 'Y')
                f2[i][j] += f1[i][j - 1];
            f3[i][j] = f3[i - 1][j] + f3[i][j - 1];
            if(a[i - 1][j] == 'Y' && a[i][j] == 'Y') 
                f3[i][j] += 2 * f2[i - 1][j] + f1[i - 1][j];
            if(a[i][j - 1] == 'Y' && a[i][j] == 'Y')
                f3[i][j] += 2 * f2[i][j - 1] + f1[i][j - 1];
        }
    // for(int i = 1; i <= n; i++) {
    //     cout << f1[i][1] << ' ' << f2[i][1] << ' ' << f3[i][1] << '\n';
    // }
    cout << f3[n][m];
	return 0;
}

牛客练习赛102 D.一人行者(换根dp)

一类环上dp解法

第十二届蓝桥杯省赛c/c++B组 括号序列

2022杭电多校 Bowcraft(概率dp)

2022杭电多校第三场 Two Permutations (dp, 哈希)

ABC160C - Power Up

题意:

给定一个长度为 n n n的序列 a [ i ] , a [ i ] ≤ 1 0 5 a[i],a[i]\leq 10^5 a[i],a[i]105.每次操作选择序列种出现次数大于1的数 x x x,选择两个,将他们删去,并把 x + 1 x+1 x+1加入序列。问最终可以形成多少种序列。

思路:

考虑dp,我们定义 d p [ i ] [ j ] : dp[i][j]: dp[i][j]:当前合并到第 i i i个数,并且多了 j j j i i i的方案数。
这样的转移看起来是 n 2 n^2 n2,但是每次转移会除2,所以实际跑起来非常快。
而且我们考虑最大会变成的数是 m a x ( a [ i ] ) + l g ( n ) max(a[i])+lg(n) max(a[i])+lg(n),所以结束状态是 d p [ m x + l g ( n ) + 1 ] [ 0 ] dp[mx+lg(n)+1][0] dp[mx+lg(n)+1][0]

int n, a[N], cnt[N];
vector<int> dp[N];
 
int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
 
    cin >> n;
    for(int i = 1; i <= n; i++) {
        cin >> a[i];
        cnt[a[i]]++;
    }
    dp[1].resize(1);
    dp[1][0] = 1;
    int mx = *max_element(a + 1, a + 1 + n);
    int m = __lg(n);
    for(int i = 1; i <= mx + m; i++) {
        int k = SZ(dp[i]);
        dp[i + 1].resize((k + cnt[i]) / 2 + 10);
        for(int j = k - 1; j >= 0; j--) {
            if(j != k - 1) {
                dp[i][j] = (dp[i][j] + dp[i][j + 1]) % mod;
            }
            dp[i + 1][(j + cnt[i]) / 2] = (dp[i + 1][(j + cnt[i]) / 2] + dp[i][j]) % mod;
        }
    }
    cout << dp[mx + m + 1][0];
 
 
	return 0;
}

思路:

2021陕西省ICPC省赛 D:Disease

思路:

向下感染是没有意义的,所以我们只关心向上感染。
我们定义 d p [ i ] dp[i] dp[i]表示 i i i节点未被感染的概率,转移很简单:

void dfs(int u, int fa) {
	dep[u] = dep[fa] + 1;
	f[u] = (1 - out[u] + mod) % mod;
	for(int i = h[u]; ~i; i = ne[i]) {
		int j = e[i];
		if(j == fa) continue;
		dfs(j, u);
		f[u] = f[u] * (f[j] + (1 - f[j] + mod) * (1 - w[i] + mod) % mod) % mod;
	}
}

然后我们枚举最小的感染层数。
i i i层未被感染的概率为: i − 1 i-1 i1层未被大自然感染的概率且第 i i i层所有节点不被感染的概率。
为什么?因为既然前 i i i层均未被感染,但是第 i i i层有被第 i + 1 i+1 i+1层传播感染的风险。

code:

#define int LL

const int N = 1e5 + 10, M = N * 2, mod = 1e9 + 7;

//第i层未被感染的概率为前i-1层未被大自然感染且第i层所有节点不被感染
int f[N], dep[N], out[N], mul[N], val[N];
int h[N], e[M], ne[M], w[M], idx;

void add(int a, int b, int c) {
	e[idx] = b; ne[idx] = h[a]; w[idx] = c; h[a] = idx++;
}
void dfs(int u, int fa) {
	dep[u] = dep[fa] + 1;
	f[u] = (1 - out[u] + mod) % mod;
	for(int i = h[u]; ~i; i = ne[i]) {
		int j = e[i];
		if(j == fa) continue;
		dfs(j, u);
		f[u] = f[u] * (f[j] + (1 - f[j] + mod) * (1 - w[i] + mod) % mod) % mod;
	}
}

signed main() {
#ifdef JANGYI
    freopen("input.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cin.tie(nullptr);
    int n;
	memset(h, -1, sizeof h);
	cin >> n;
	rep(i, 1, n) {
		int x, y; cin >> x >> y;
		out[i] = x * qpow(y, mod - 2, mod) % mod;
	}
	rep(i, 1, n - 1) {
		int u, v, x, y; 
		cin >> u >> v >> x >> y;
		add(u, v, x * qpow(y, mod - 2, mod) % mod); 
		add(v, u, x * qpow(y, mod - 2, mod) % mod);
	}
	dfs(1, 0);
	rep(i, 0, n) mul[i] = 1;
	for(int i = 1; i <= n; i++) {
		mul[dep[i]] = mul[dep[i]] * (1 - out[i] + mod) % mod;
	}
	for(int i = 1; i <= n; i++) mul[i] = mul[i - 1] * mul[i] % mod;
	val[0] = 1;
	for(int i = 1; i <= n; i++) val[i] = mul[i - 1];
	for(int i = 1; i <= n; i++) {
		val[dep[i]] = val[dep[i]] * f[i] % mod;
	}
	LL ans = 0;
	//val[i - 1] : 第i层未被感染的概率和第i层被感染但是未传到第i-1层的概率
	//val[i] : 
	for(int i = 1; i <= n; i++) {
		ans += 1LL * i * (val[i - 1] - val[i] + mod) % mod;
		ans %= mod;
	}
	cout << ans % mod << '\n';

    return 0;
}

第16届商汤杯BCPC H.莫卡与阿拉德大陆

题意:

给定n个园,每输入一个圆,该圆覆盖的面积会反转一次颜色(黑白),起初平面都是白色。可以使用任意次魔法,一次魔法可以操作一个圆,也就是再对其反转一次。给定 n , k n,k n,k,问最少使用多少次魔法后黑色面积 ≤ k π \leq k \pi

思路:

一个圆最多也只能操作一次,而且根据这种圆与圆的包含关系,我们可以建成树形结构,类似背包,在树中跑背包即可。
我们定义 f [ i ] [ j ] [ 0 / 1 ] f[i][j][0/1] f[i][j][0/1]表示在以 i i i为根的子树上选择 j j j个圆反转后的最大 黑色 / 白色 黑色/白色 黑色/白色 面积。加入上下界优化后可以优化到 O ( n 2 ) O(n^2) O(n2)

code:

const int N = 2e3 + 10, M = N * 2, mod = 1e9 + 7;

#define int LL
int n; LL m;
vector<int> G[N];
struct Node {
    LL x, y, r;
}a[N];
LL val[N];
LL f[N][N][2]; //f[i][j][0/1]表示以i为根的子树中选j个点反转的最大 黑色/白色 面积
int sz[N];
LL g[N][N][2];
LL temp[N][2];
void dfs(int u) {
    for(auto son : G[u]) {
        dfs(son);
        for(int i = 0; i <= sz[u] + sz[son]; i++) temp[i][0] = temp[i][1] = 0;
        for(int j = 0; j <= sz[u]; j++) 
            for(int k = 0; k <= sz[son]; k++) {
                temp[j + k][0] = max(temp[j + k][0], f[u][j][0] + f[son][k][0]);
                temp[j + k][1] = max(temp[j + k][1], f[u][j][1] + f[son][k][1]);
            }
        for(int i = 0; i <= sz[u] + sz[son]; i++)
            f[u][i][0] = temp[i][0], f[u][i][1] = temp[i][1];
        sz[u] += sz[son];
    }
    if(u == n + 1) return;
    sz[u]++;
    //temp数组为不加父节点之前的答案贡献
    for(int i = 0; i <= sz[u]; i++) {
        temp[i][0] = f[u][i][0], temp[i][1] = f[u][i][1];
    }
    //加入父节点后的贡献
    //不操作u
    for(int i = 0; i <= sz[u] - 1; i++) {
        f[u][i][0] = temp[i][1] + val[u];
        f[u][i][1] = temp[i][0];
    }
    //操作u
    for(int i = 1; i <= sz[u]; i++) {
        f[u][i][0] = max(f[u][i][0], temp[i - 1][0]);
        f[u][i][1] = max(f[u][i][1], temp[i - 1][1] + val[u]);
    }
}

signed main() {
#ifdef JANGYI
    freopen("input.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cin.tie(nullptr);

    cin >> n >> m;
    for(int i = 1; i <= n; i++) {
        cin >> a[i].x >> a[i].y >> a[i].r;
    }
    sort(a + 1, a + 1 + n, [](Node x, Node y) {
        return x.r > y.r;
    });
    auto check = [&](Node x, Node y) -> bool {
        return (x.x - y.x) * (x.x - y.x) + (x.y - y.y) * (x.y - y.y) < (x.r + y.r) * (x.r + y.r);
    };
    for(int i = 1; i <= n; i++) val[i] = a[i].r * a[i].r;
    for(int i = 1; i <= n; i++) {
        bool vis = 0;
        for(int j = i - 1; j >= 1; j--) 
            if(check(a[i], a[j])) {
                vis = 1;
                G[j].push_back(i);
                val[j] -= val[i];
                break;
            }
        if(!vis) G[n + 1].push_back(i);
    }
    dfs(n + 1);
    LL ans = 0;
    for(int i = 1; i <= n; i++) ans += val[i];
    for(int i = 0; i <= n; i++) {
        if(ans - f[n + 1][i][1] <= m) {
            cout << i << '\n';
            break;
        }
    }
    return 0;
}
/*
*/

2022湖北省赛 D. Transition

题意:

给定两个01串 a , b a,b a,b。操作1:选择两个位置交换 ( a [ i ] , a [ j ] ) (a[i],a[j]) (a[i],a[j]),代价为 ∣ i − j ∣ |i-j| ij;操作2:将 a [ i ] = a [ i ] X O R 1 a[i]=a[i]XOR1 a[i]=a[i]XOR1,代价为 1 1 1。询问在把 a 变成 b a变成b a变成b的最小代价的前提下不同操作集合的数量。

思路:

我们贪心的发现要操作的话尽量选择位置差小的操作,更严谨的说,对于位置 i i i,它最远与 i − 2 i-2 i2进行操作。我们仿照最长上升子序列的方式转移即可。

code:

const int N = 3e5 + 10, M = N * 2, mod = 1e9 + 7;

LL dp[N], way[N];
int n;
char a[N], b[N];
void upd(int x, int y, int c) {
    if(dp[x] > dp[y] + c) {
        dp[x] = dp[y] + c;
        way[x] = 0;
    }
    if(dp[x] == dp[y] + c) {
        way[x] = (way[x] + way[y]) % mod;
    }
}
signed main() {
#ifdef JANGYI
    freopen("input.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif
    scanf("%d", &n);
    scanf("%s", a + 1); 
    scanf("%s", b + 1);
    way[0] = 1;
    for(int i = 1; i <= n; i++) {
        dp[i] = n + 1;
        if(a[i] == b[i]) upd(i, i - 1, 0);
        if(a[i] != b[i]) upd(i, i - 1, 1);
        if(i >= 2 && a[i] != b[i] && a[i - 1] != b[i - 1] && a[i] != a[i - 1])
            upd(i, i - 2, 1);
        if(i >= 3 && a[i] != b[i] && a[i - 2] != b[i - 2] && a[i] != a[i - 2]) {
            if(a[i - 1] == b[i - 1]) upd(i, i - 3, 2), upd(i, i - 3, 2);
        }
    }
    cout << way[n] << endl;
    return 0;
}
/*
*/

Codeforces Round #815 (Div. 2) D. Xor-Subsequence

题意:

给定一个长度为 n ( n ≤ 3 e 5 ) n(n\leq3e5) n(n3e5)的数组 a a a,下标从0开始。找一个 0 − n − 1 0-n-1 0n1的自然排列的子序列 b b b,满足 a [ b [ i ] ] ⊕ b [ i + 1 ] < a [ b [ i + 1 ] ] ⊕ b [ i ] a[b[i]]\oplus b[i+1]< a[b[i+1]]\oplus b[i] a[b[i]]b[i+1]<a[b[i+1]]b[i]。求 b b b的最大长度。
对于 D 1 : 0 ≤ a [ i ] ≤ 200 ; D 2 : 0 ≤ a [ i ] ≤ 1 e 9 D1:0\leq a[i]\leq 200;D2:0\leq a[i]\leq 1e9 D1:0a[i]200;D2:0a[i]1e9

思路:

先考虑 D 1 : D1: D1:我们读完题意发现就是个最长上升子序列,那么我们可以很轻易写出以下代码:

	for(int i = 0; i < n; i++) {
            for(int j = i - 1; j >= 0; j--) {
                if((a[j] ^ i) < (a[i] ^ j)) dp[i] = max(dp[i], dp[j] + 1);
            }
        }

很明显会 T T T,但是我们思考 a [ i ] ≤ 200 a[i]\leq200 a[i]200这个条件有什么用。当 i , j i,j i,j相差比较大时, a [ j ] ⊕ i a[j]\oplus i a[j]i肯定是 ≥ a [ i ] ⊕ j \geq a[i]\oplus j a[i]j的。那么我们向前跑个500次就差不多了。
再考虑 D 2 D2 D2,也就是如何将上面的代码优化到 n l o g nlog nlog级别。如果满足上面条件的话,那么一定是二者二进制下的前 K K K位是相等的。也就是存在第 K + 1 K+1 K+1位 : a [ j ] ⊕ i = 0 , a [ i ] ⊕ j = 1 a[j]\oplus i=0,a[i]\oplus j=1 a[j]i=0,a[i]j=1;前 K K K位, a [ j ] ⊕ i = a [ i ] ⊕ j a[j]\oplus i = a[i]\oplus j a[j]i=a[i]j,两边同时异或 i ⊕ j i\oplus j ij,即 a [ j ] ⊕ j = a [ i ] ⊕ i a[j]\oplus j = a[i]\oplus i a[j]j=a[i]i
也就是在第 k + 1 k +1 k+1位上, a [ j ] = i , a [ i ] ≠ j a[j]=i,a[i]\neq j a[j]=i,a[i]=j,我们枚举以下所有情况后发现此时同样满足 a [ i ] ⊕ i ≠ a [ j ] ⊕ j a[i]\oplus i\neq a[j]\oplus j a[i]i=a[j]j。那么我们就可以维护一个字典树,维护 a [ i ] ⊕ i a[i]\oplus i a[i]i。不要忘记我们还需满足该位 a [ i ] ≠ j a[i]\neq j a[i]=j,所以对于字典树的每个节点我们还需记录该位的两个状态(0,1),插入查询的时候多传入参数即可。具体可看代码

code:

 
const int N = 6e6 + 10, M = N * 2, mod = 1e9 + 7;
 
int tr[N][2], idx = 1, f[N][2];
int dp[300005];
 
void insert(int x, int id) {
    int now = 1;
    per(i, 30, 0) {
        int ch = x >> i & 1;
        if(!tr[now][ch]) tr[now][ch] = ++idx;
        now = tr[now][ch];
        f[now][(id >> i) & 1] = max(f[now][(id >> i) & 1], dp[id]);
    }
}
int query(int x, int id) {
    int now = 1, res = 1;
    per(i, 30, 0) {
        int ch = x >> i & 1; //a[i] ^ i
        int now_id = tr[now][ch ^ 1];//a[i] ^ i ^ 1
        //需满足该位a[j]!=i
        res = max(res, f[now_id][(id >> i) & 1 ^ 1] + 1);
        if(!tr[now][ch]) break;
        now = tr[now][ch];
    }
    return res;
}
signed main() {
#ifdef JANGYI
    freopen("input.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cin.tie(nullptr);
    
    int T;
    cin >> T;
    while(T--) {
        int n; cin >> n;
        vector<int> a(n);
        rep(i, 0, n) dp[i] = 1;
        rep(i, 0, n - 1) cin >> a[i];
        rep(i, 0, n - 1) {
            dp[i] = query(a[i] ^ i, a[i]);
            insert(a[i] ^ i, i);
        }
 
        cout << *max_element(dp, dp + n) << '\n';
        rep(i, 0, idx) f[i][0] = f[i][1] = tr[i][0] = tr[i][1] = 0;  
        idx = 1;
    }
 
    
    return 0;
}

Croc Champ 2012 - Round 2 B. Word Cut

题意:

给定两个串,每次操作可选择一个分界点: S = X Y S=XY S=XY,对其交换,使得 S = Y X S=YX S=YX,问操作 K K K次后一个串变成另一个串的方案数。

思路:

我们发现这个操作其实就是把一个串首位相连成环,然后选择一个起点变成新串。那么,如果 A A A串显然可以通过一次操作就变成 B B B串,否则无论如何都变不成。那么我们 d p [ i ] [ 0 / 1 ] dp[i][0/1] dp[i][0/1]表示进行 i i i次操作, A A A未变成/变成 B B B串的方案数。
我们设 x x x为串 A A A中可以通过一步就变成 B B B串的位置数。
那么 f [ i ] [ 0 ] = f [ i − 1 ] [ 0 ] ∗ ( n − 1 − x ) + f [ i − 1 ] [ 1 ] ∗ ( n − x ) f [ i ] [ 1 ] = f [ i − 1 ] [ 1 ] ∗ ( x − 1 ) + f [ i − 1 ] [ 0 ] ∗ x f[i][0]=f[i-1][0]*(n-1-x)+f[i-1][1]*(n-x)\\ f[i][1]=f[i-1][1]*(x-1)+f[i-1][0]*x f[i][0]=f[i1][0](n1x)+f[i1][1](nx)f[i][1]=f[i1][1](x1)+f[i1][0]x

code:

const int N = 1e5 + 10, M = N * 2, mod = 1e9 + 7;

mint f[N][2]; //f[i][0/1]前i次操作 未变成/变成 的方案
int k; 
string a, b;

signed main() {
#ifdef JANGYI
    freopen("input.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cin.tie(nullptr);
    
    cin >> a >> b >> k;
    // a = ' ' + a; b = ' ' + b;
    int n = a.size();
    if(a.size() != b.size()) {
        cout << 0 << '\n';
        return 0;
    }
    bool flag = 0;
    int x = 0;
    rep(i, 0, n - 1) {
        string now = a.substr(i) + a.substr(0, i);
        if(now == b) flag = 1, x++;
    }
    if(!flag) {
        cout << 0 << '\n';
        return 0;
    }
    if(a == b) f[0][1] = 1;
    else f[0][0] = 1;
    rep(i, 1, k) {
        f[i][1] = f[i - 1][1] * (x - 1) + f[i - 1][0] * x;
        f[i][0] = f[i - 1][0] * (n - 1 - x) + f[i - 1][1] * (n - x);
    }

    cout << f[k][1] << '\n';

    return 0;
}

Codeforces Round #821 (Div. 2) D2. Zero-One

题意:

给定两个01串 S , T S,T S,T,每次可以选择 S S S串中的两个位置,将它们的值异或1,如果相邻,必须选择花费 X X X的代价,不相邻可以选择花费 Y Y Y的代价,问最小的代价将 S 串变成 T 串 S串变成T串 S串变成T.

思路:

如果两个位置相邻,我们的花费应该是min(x, 2 * y)
如果不相邻,我们的花费应该是:min(y, (r - l) * x).
直接记忆化搜索即可。

code:

const int N = 5e3 + 10, M = N * 2, mod = 1e9 + 7;

LL n, x, y;
string a, b;
LL f[N][N];
LL get(int l, int r) {
    if(l + 1 == r) return min(x, 2 * y);
    else return min((r - l) * x, y);
}
vector<int> pos;
LL dfs(int l, int r) {
    if(l > r) return 0;
    if(f[l][r] != -1) return f[l][r];
    LL res = INF;
    res = min(res, dfs(l + 1, r - 1) + get(pos[l], pos[r]));
    res = min(res, dfs(l, r - 2) + get(pos[r - 1], pos[r]));
    res = min(res, dfs(l + 2, r) + get(pos[l], pos[l + 1]));
    return f[l][r] = res;
}
signed main() {
#ifdef JANGYI
    freopen("input.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cin.tie(nullptr);
    int T; cin >> T;
    while(T--) {
        cin >> n >> x >> y;
        cin >> a >> b; a = ' ' + a, b = ' ' + b;
        pos.clear();
        rep(i, 1, n) if(a[i] != b[i]) pos.pb(i);
        rep(i, 0, n) rep(j, 0, n) f[i][j] = -1;
       
        if(pos.size() % 2) {
            cout << -1 << "\n";
            continue;
        }
        if(x >= y) {
            if(pos.size() == 2) {
                if(pos[0] + 1 == pos[1]) cout << min(2 * y, x) << '\n';
                else cout << y << '\n';
            } else {
                cout << pos.size() / 2 * y << '\n';
            }
        } else {
            if(pos.size() == 2) {
                if(pos[0] + 1 == pos[1]) cout << min(2 * y, x) << '\n';
                else cout << min((pos[1] - pos[0]) * x, y) << '\n';
            } else {
                cout << dfs(0, pos.size() - 1) << '\n';
            }
        }
    }
}

2022icpc网络赛第一场L:LCS-like Problem

题意:

给定两个串 s , t s,t s,t,询问 s s s中的一个最长的子串 p p p的长度,子串 p p p需满足与 t t t串的任意公共子串的长度 ≤ 1 \leq1 1

思路:

我们先预处理出来一个ban[a][b]数组,表示选取的 p p p中不能存在a.....b这样的子串。

for(int i = m; i >= 1; i--) {
        for(int j = 0; j < 26; j++) 
            if(vis[j]) ban[t[i] - 'a'][j] = 1;
        vis[t[i] - 'a'] = 1;
    }

接下来考虑 d p dp dp, d p [ i ] [ j ] dp[i][j] dp[i][j]表示在 s s s串中的前 i i i个字符中选,最后一个在 t t t中出现的字符为 j j j的最长子串。若 s [ i ] s[i] s[i]没有在 t t t中出现过,则f[i][j] = f[i - 1][j] + 1;否则我们枚举 j j j,把 s [ i ] s[i] s[i] j j j后面,满足ban[j][s[i]]=0f[i][s[i]] = f[i - 1][j] + 1

code:

bool vis[29];
bool ban[29][29];
int f[N][29];

signed main() {
#ifdef JANGYI
    freopen("input.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cin.tie(nullptr);

    string s, t; cin >> s >> t;
    s = ' ' + s; t = ' ' + t;
    int n = s.size() - 1, m = t.size() - 1;
    for(int i = m; i >= 1; i--) {
        for(int j = 0; j < 26; j++) 
            if(vis[j]) ban[t[i] - 'a'][j] = 1;
        vis[t[i] - 'a'] = 1;
    }
    for(int i = 1; i <= n; i++) {
        for(int j = 0; j < 26; j++) {
            f[i][j] = f[i - 1][j] + (vis[s[i] - 'a'] == 0);
        }
        if(vis[s[i] - 'a'] == 0) continue;
        f[i][s[i] - 'a'] = max(f[i][s[i] - 'a'], 1);
        for(int j = 0; j < 26; j++) {
            if(!ban[j][s[i] - 'a'])
                f[i][s[i] - 'a'] = max(f[i][s[i] - 'a'], f[i - 1][j] + 1);
        }
    }   
    int ans = 0;
    for(int i = 0; i < 26; i++) 
        ans = max(ans, f[n][i]);
    cout << ans << '\n'; 
            
    return 0;
}

最大连续子段和

思路:

简化完题意后发现让求的就是前 i i i天的最大连续子段和的的数量。那么我们思考 i − 1 和 i 天 i-1和i天 i1i怎么去转移。分为两种情况:最大连续子段和改变或不变。若改变则说明以 i i i结尾的后缀和大于当前的最大子段和,也就是当前的前缀和减去前缀最小值大于当前的最大子段和。若不改变则直接转移。

code:

int n, m; cin >> n >> m;
    vector<int> a(n + 1);
    rep(i, 1, n) cin >> a[i];
    LL sum = 0;
    vector<int> f(n + 1);
    //f[i]表示前i天最大子段和的数量
    LL cntmn = 1, mn = 0, mx = -INF;
    rep(i, 1, n) {
        a[i] += a[i - 1];
        if(a[i] - mn > mx) {
            f[i] = cntmn;
            mx = a[i] - mn;
        } else {
            if(a[i] - mn == mx) {
                f[i] = f[i - 1] + cntmn;
            } else {
                f[i] = f[i - 1];
            }
        }
        if(a[i] < mn) mn = a[i], cntmn = 1;
        else if(a[i] == mn) cntmn++;
    }
    while(m--) {
        int x; cin >> x;
        cout << ans[x] << '\n';
    }

Codeforces Round #820 (Div. 3) G.Cut Substrings

题意:

给定串S,T,当S中存在与T相互匹配的子串我们需要将其删除,但删除后S串不合并。求最小删除次数以及在其前提下剩余多少种不同的子串.

思路:

数据 n ≤ 500 n\leq 500 n500,那么我们应该是可以随便乱搞的。我们先找出每个 t t t s s s串中的结束位置,可以 K M P KMP KMP,但属实没必要。那么我们设 f [ i ] f[i] f[i]表示消除 s s s中前 i i i个出现的 t t t串,且第 i i i个串必须操作一次的最小次数。那么我们什么可以进行 f [ i ] = f [ j ] + 1 f[i]=f[j]+1 f[i]=f[j]+1的转移?我们画几个例子就可以看出:当且仅当 i , j i,j i,j这两个串没有重叠,且如果 i , j i,j ij之间也存在另一个子串 k k k的话, k 和 i , k 和 j k和i,k和j ki,kj也不重叠。那么怎么计算答案?如果第 i i i个位置与最后一个位置有重叠,那么这个位置就可以被作为答案。

code:


const int N = 2e5 + 10, M = N * 2, mod = 998244353;

int f[555];
mint cnt[555];

signed main() {
#ifdef JANGYI
    freopen("input.in", "r", stdin);
    freopen("out.out", "w", stdout);
#endif
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cin.tie(nullptr);
    int T;
    cin >> T;
    while(T--) {    
        memset(cnt, 0, sizeof cnt);
        vector<int> pos; pos.pb(0);
        string s, t;
        cin >> s >> t; 
        rep(i, 0, 500) f[i] = inf;
        int n = s.size(), m = t.size();
        for(int i = 0; i + m - 1 < n; i++) 
            if(s.substr(i, m) == t) pos.pb(i + m);
        cnt[0] = 1;
        f[0] = 0;
        for(int i = 1; i < pos.size(); i++) {
            for(int j = 0; j < i; j++) {
                if(pos[i] - pos[j] < m) continue;
                bool flag = 1;
                for(int k = j + 1; k < i; k++) {
                    if(pos[i] - pos[k] >= m && pos[k] - pos[j] >= m) {
                        flag = 0;
                        break;
                    }
                }
                if(!flag) continue;
                if(f[i] > f[j] + 1) {
                    f[i] = f[j] + 1;
                    cnt[i] = cnt[j];
                } else if(f[i] == f[j] + 1) {
                    cnt[i] += cnt[j];
                }
            }
        }
        int mn = inf; mint ans = 0;
        rep(i, 0, pos.size() - 1) {
            if(pos.back() - pos[i] < m) 
                mn = min(mn, f[i]);
        }
        rep(i, 0, pos.size() - 1) {
            if(pos.back() - pos[i] < m && f[i] == mn) ans += cnt[i];
        }
        cout << mn << ' ' << ans << '\n';
    }

    return 0;
}

Two out of Three

思路:

读完题确实很容易的想到几个状态,但很明显的都有后效性,再瞅一眼题目范围 n ≤ 1000 n\leq1000 n1000,觉得有点离谱,要 n 2 n^2 n2 d p dp dp???最后瞟了一眼题解。
题目的操作是每3个人中选两个人,然后一个人剩下,我们再手玩一下样例,发现第 i i i次结账后,剩下的人一定是一个 x x x [ 2 ∗ i + 2 , n ] [2*i+2,n] [2i+2,n],那么我们设状态 f [ i ] [ j ] f[i][j] f[i][j]表示考虑到第 i i i次结账,第 j j j个人被留了下来的最短时间。转移的话枚举 j , 2 ∗ i + 1 , 2 ∗ i j,2*i+1,2*i j,2i+1,2i这三个人选哪两个转移即可。我们设 a [ n + 1 ] = I N F a[n+1]=INF a[n+1]=INF,这样的话如果 n n n是偶数,我们直接输出 f [ n / 2 ] [ n + 1 ] f[n/2][n+1] f[n/2][n+1],让前 n n n个人结账,第 n + 1 n+1 n+1个人最多的时间留下来。如果 n n n是奇数,我们输出 f [ n / 2 ] [ j ] f[n/2][j] f[n/2][j],枚举 j j j

code:

const int N = 1e3 + 10, M = N * 2, mod = 998244353;

int f[N][N];
int n, a[N];
int f_pos[N][N], idx[N][N][2];
int ans[N][2];
signed main() {
    //freopen("input.txt", "r", stdin);
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin.tie(nullptr);
    cin >> n;
    rep(i, 1, n) cin >> a[i];
    a[n + 1] = inf;
    memset(f, 0x3f, sizeof f);
    f[0][1] = 0;
    for (int i = 1; i <= n / 2; i++) {
        int x = 2 * i, y = 2 * i + 1;
        for (int j = 1; j < 2 * i; j++) {
            if (f[i][j] > f[i - 1][j] + max(a[x], a[y])) {
                f[i][j] = f[i - 1][j] + max(a[x], a[y]);
                f_pos[i][j] = j;
                idx[i][j][0] = x;
                idx[i][j][1] = y;
            }
            if (f[i][x] > f[i - 1][j] + max(a[y], a[j])) {
                f[i][x] = f[i - 1][j] + max(a[y], a[j]);
                f_pos[i][x] = j;
                idx[i][x][0] = y;
                idx[i][x][1] = j;
            }
            if (f[i][y] > f[i - 1][j] + max(a[x], a[j])) {
                f[i][y] = f[i - 1][j] + max(a[x], a[j]);
                f_pos[i][y] = j;
                idx[i][y][0] = x;
                idx[i][y][1] = j;
            }
        }
    }
    if (n % 2 == 0) {
        cout << f[n / 2][n + 1] << '\n';
        int now = n + 1;
        per(i, n / 2, 1) {
            ans[i][0] = min(idx[i][now][0], idx[i][now][1]);
            ans[i][1] = max(idx[i][now][1], idx[i][now][0]);
            now = f_pos[i][now];
        }
        rep(i, 1, n / 2) cout << ans[i][0] << ' ' << ans[i][1] << '\n';
    } else {
        int mx = inf, id;
        for (int j = 1; j <= n; j++) {
            if (f[n / 2][j] + a[j] < mx) {
                mx = f[n / 2][j] + a[j];
                id = j;
            }
        }
        cout << mx << '\n';
        int now = id;
        per(i, n / 2, 1) {
            ans[i][0] = min(idx[i][now][0], idx[i][now][1]);
            ans[i][1] = max(idx[i][now][0], idx[i][now][1]);
            now = f_pos[i][now];
        }
        rep(i, 1, n / 2) cout << ans[i][0] << ' ' << ans[i][1] << '\n';
        cout << id;
    }
    return 0;
}
/*

*/

Fence Job(前缀和优化)

思路:

首先读完题可发现的是每个 h [ i ] h[i] h[i]都有个极长区间,也就是 h [ i ] h[i] h[i]只能在这个区间内出现,且作为该区间内的极小值。
看数据应该是 n 2 n^2 n2 d p dp dp,考虑从什么状态入手。我们发现不同的操作可能会出现相同的结果序列,那么我们从操作完最后的序列入手。 d p [ i ] [ j ] dp[i][j] dp[i][j]表示操作前 i i i个数,且最后一个数以 a [ j ] a[j] a[j]结尾的合法序列有多少种。考虑转移:如果最后一个数以 a [ j ] a[j] a[j]结尾,那么前一个数只能以 x x x结尾且 x ≤ a [ j ] x\leq a[j] xa[j]
这么 d p [ i ] [ j ] = ∑ x = 1 j d p [ i − 1 ] [ x ] , a [ x ] ≤ a [ j ] dp[i][j]=\sum_{x=1}^{j}{dp[i-1][x]},a[x]\leq a[j] dp[i][j]=x=1jdp[i1][x],a[x]a[j].用前缀和优化转移即可。

code:

 	cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i];
    for(int i = 1; i <= n; i++) {
        for(int j = i + 1; j <= n + 1; j++) 
            if(a[j] < a[i]) {
                r[i] = j;
                break;
            }
        for(int j = i - 1; j >= 0; j--) 
            if(a[j] < a[i]) {
                l[i] = j;
                break;
            }
    }
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= n; j++) {
            if(i == 1) {
                if(l[j] < i && i < r[j]) 
                    dp[i][j] = 1;
            } else {
                if(l[j] < i && i < r[j]) {
                    dp[i][j] += sum[i - 1][j];
                }
            }
            sum[i][j] = sum[i][j - 1] + dp[i][j];
        }
    }
    mint ans = 0;
    for(int i = 1; i <= n; i++) ans += dp[n][i];
    cout << ans << '\n';

Yaroslav and Two Strings 线性dp

思路:

很裸的线性 d p dp dp啊,发现状态无非就那么几个:
f [ i ] [ 0 ] : 前 i 个数仅有 s [ j ] ≤ w [ j ] f[i][0]:前i个数仅有s[j]\leq w[j] f[i][0]:i个数仅有s[j]w[j]
f [ i ] [ 1 ] : 前 i 个数有 s [ j ] < w [ j ] & & s [ j ] > w [ j ] f[i][1]:前i个数有s[j]<w[j] \&\& s[j]>w[j] f[i][1]:i个数有s[j]<w[j]&&s[j]>w[j]
f [ i ] [ 2 ] : 前 i 个数仅有 w [ j ] ≤ s [ j ] f[i][2]:前i个数仅有w[j]\leq s[j] f[i][2]:i个数仅有w[j]s[j]
f [ i ] [ 3 ] : 前 i 个数仅有 s [ j ] = w [ j ] f[i][3]:前i个数仅有s[j]=w[j] f[i][3]:i个数仅有s[j]=w[j]
暴力转移即可

code:

mint f[N][4];
signed main() {
#ifdef JANGYI
    freopen("input.in", "r", stdin);
    freopen("out.out", "w", stdout);
    auto now = clock();
#endif  
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin >> n;
    string s, w;
    cin >> s >> w;
    s = ' ' + s; w = ' ' + w;
    f[0][3] = 1;
    for(int i = 1; i <= n; i++) {
        if(s[i] == '?' && w[i] == '?') {
            for(int x = 0; x <= 9; x++)
                for(int y = 0; y <= 9; y++) {
                    if(x < y) {
                        f[i][0] += f[i - 1][0] + f[i - 1][3];
                        f[i][1] += f[i - 1][1] + f[i-  1][2];
                    } else if(x > y) {
                        f[i][1] += f[i - 1][1] + f[i - 1][0];
                        f[i][2] += f[i - 1][2] + f[i - 1][3];
                    } else {
                        f[i][1] += f[i - 1][1];
                        f[i][2] += f[i - 1][2];
                        f[i][0] += f[i - 1][0];
                        f[i][3] += f[i - 1][3];
                    }
                }
        }
        if(s[i] != '?' && w[i] != '?') {
            int x = s[i] - '0', y = w[i] - '0';
            if(x > y) {
                f[i][0] = 0;
                f[i][1] = f[i - 1][1] + f[i - 1][0];
                f[i][2] = f[i - 1][2] + f[i - 1][3];
                f[i][3] = 0;
            } else if(x == y) {
                f[i][0] = f[i - 1][0];
                f[i][1] = f[i - 1][1];
                f[i][2] = f[i - 1][2];
                f[i][3] = f[i - 1][3];
            } else {
                f[i][0] = f[i - 1][0] + f[i - 1][3];
                f[i][1] = f[i - 1][1] + f[i - 1][2];
                f[i][2] = 0;
                f[i][3] = 0;
            }
        }
        if(s[i] != '?' && w[i] == '?') {
            int x = s[i] - '0';
            for(int y = 0; y <= 9; y++) {
                if(x > y) {
                    f[i][1] += f[i - 1][1] + f[i - 1][0];
                    f[i][2] += f[i - 1][2] + f[i - 1][3];
                } else if(x == y) {
                    f[i][0] += f[i - 1][0];
                    f[i][1] += f[i - 1][1];
                    f[i][2] += f[i - 1][2];
                    f[i][3] += f[i - 1][3];
                } else {
                    f[i][0] += f[i - 1][0] + f[i - 1][3];
                    f[i][1] += f[i - 1][1] + f[i - 1][2];
                }
            }
        }
        if(s[i] == '?' && w[i] != '?') {
            int y = w[i] - '0';
            for(int x = 0; x <= 9; x++) {
                if(x > y) {
                    f[i][1] += f[i - 1][1] + f[i - 1][0];
                    f[i][2] += f[i - 1][2] + f[i - 1][3];
                } else if(x == y) {
                    f[i][0] += f[i - 1][0];
                    f[i][1] += f[i - 1][1];
                    f[i][2] += f[i - 1][2];
                    f[i][3] += f[i - 1][3];
                } else {
                    f[i][0] += f[i - 1][0] + f[i - 1][3];
                    f[i][1] += f[i - 1][1] + f[i - 1][2];
                }
            }
        }
        if(s[i] != '?' && w[i] != '?') {
            int x = s[i] - '0', y = w[i] - '0';
            if(x > y) {
                f[i][1] = f[i - 1][1] + f[i - 1][0];
                f[i][2] = f[i - 1][2] + f[i - 1][3];
                f[i][3] = 0;
            } else if(x == y) {
                f[i][0] = f[i - 1][0];
                f[i][1] = f[i - 1][1];
                f[i][2] = f[i - 1][2];
                f[i][3] = f[i - 1][3];
            } else {
                f[i][0] = f[i - 1][0] + f[i - 1][3];
                f[i][1] = f[i - 1][1] + f[i - 1][2];
                f[i][2] = 0;
                f[i][3] = 0;
            }
        }
        // for(int j = 0; j < 4; j++) D(f[i][j])
    }
    cout << f[n][1];
#ifdef JANGYI
    cerr << "================================" << endl;
    cerr << "Program run for " << (clock() - now) / (double)CLOCKS_PER_SEC * 1000 << " ms." << endl;
#endif
    return 0;
}   
/*
仅有:
f[i][0] : s[i] <= w[i];
f[i][1] : s[i] < w[i] & s[i] > w[i];
f[i][2] : s[i] >= w[i];
f[i][3] : s[i] == w[i];
 
*/

杭电多校第三场Two Permutations(哈希或者记忆化搜索dp)

题意:

给定两个全排列数组 P , Q P,Q P,Q,和一个空数组 R R R,每次从两个数组的首位数字挑一个加到 R R R后面,然后删除该数组。问:给定最后形成的 R R R,求有多少种方案可以构成。

解法一:

我们会发现一个数字会出现两次,那么我们记录每个数字在 R R R中第一次,第二次出现的位置。对于当前数字 p [ i ] p[i] p[i],枚举数字 p [ i + 1 ] p[i+1] p[i+1]出现的位置,那么这两个位置中间就要放 Q Q Q的一段,用哈希判断是否和 R R R这一段子串完全匹配即可。

code:

typedef unsigned long long ULL;
typedef long long LL;
typedef pair<int, int> pii;
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
} 
const int N = 3e5 + 10, M = N * 2, mod1 = 998244353, mod2 = 1e9 + 7;
inline LL ksm(LL a, LL b, int mod){
    LL ans = 1; 
    for(; b; b >>= 1, a = a * a % mod) if(b & 1) ans = ans * a % mod;
    return ans;
}
int dx[] = {1, -1, 0, 0};
int dy[] = {0, 0, -1, 1};

//----------------------------------------------------------------------------------------//
ULL fb[N], fc[N << 1], p[N << 1];
int n, a[N], b[N], c[N << 1];
int dp[N][2], pos[N][2];

inline ULL get(ULL f[], int l, int r) {
    return f[r] - f[l - 1] * p[r - l + 1];
}
inline bool check(int lb, int rb, int lc, int rc) {
    if(lb > rb) return 1;
    if(lb < 1 || rb > n || lc < 1 || rc > n + n) return 0;
    return get(fb, lb, rb) == get(fc, lc, rc);
}
inline void up(int &x, int y) {
    x = x + y >= mod1 ? x + y - mod1 : x + y;
}
void solve() {
    read(n);
    for(int i = 1; i <= n; i++) read(a[i]);
    for(int i = 1; i <= n; i++) read(b[i]);
    for(int i = 1; i <= n + n; i++) read(c[i]);

    for(int i = 1; i <= n; i++) for(int j = 0; j < 2; j++) dp[i][j] = 0;
    for(int i = 1; i <= n; i++) pos[i][0] = pos[i][1] = 0;

    for(int i = 1; i <= n; i++) fb[i] = fb[i - 1] * 233 + b[i];
    for(int i = 1; i <= n << 1; i++) fc[i] = fc[i - 1] * 233 + c[i];
    for(int i = 1; i <= n << 1; i++) {
        if(!pos[c[i]][0]) pos[c[i]][0] = i;
        else pos[c[i]][1] = i;
    }
    int Q = 1;
    for(; Q <= n; Q++) if(!pos[Q][0] || !pos[Q][1]) break;
    if(Q != n + 1) {
        puts("0");
        return;
    }
    for(int j = 0; j < 2; j++) {
        int x = pos[a[1]][j];
        if(check(1, x - 1, 1, x - 1)) dp[1][j] = 1;
    }
    for(int i = 1; i < n; i++) {
        for(int j = 0; j < 2; j++) {
            if(dp[i][j]) {
                int x = pos[a[i]][j];
                for(int k = 0; k < 2; k++) {
                    int y = pos[a[i + 1]][k];
                    if(y <= x) continue;
                    if(check(x - i + 1, y - i - 1, x + 1, y - 1)) 
                        up(dp[i + 1][k], dp[i][j]);
                }
            }
        }
    }
    int ans = 0;
    for(int j = 0; j < 2; j++) 
        if(dp[n][j]) {
            int x = pos[a[n]][j];
            if(check(x - n + 1, n, x + 1, n + n )) up(ans, dp[n][j]);
        }
    printf("%d\n", ans);
}   
signed main() {
#ifdef JANGYI
    freopen("input.in", "r", stdin);
    freopen("out.out", "w", stdout);
    auto now = clock();
#endif
    // ios::sync_with_stdio(false);
    // cin.tie(0);
    p[0] = 1;
    for(int i = 1; i < N << 1; i++) p[i] = p[i - 1] * 233;
    int T = 1;
    read(T);
    while(T--) {
        solve();
    }    

#ifdef JANGYI
    cout << "================================" << endl;
    cout << "Program run for " << (clock() - now) / (double)CLOCKS_PER_SEC * 1000 << " ms." << endl;
#endif
    return 0;
}   

解法二:

我们定义 d p [ i ] [ 0 / 1 ] dp[i][0/1] dp[i][0/1]表示 R R R的第 i i i位与 P / Q P/Q P/Q匹配的方案数。采用记忆化搜索的方法实现即可。

code:

int dp[2 * MAXN][2];

int dfs(int x, int y,int tar) {
    if (x > n && y > n)return 1;
    if (dp[x + y - 1][tar]!=-1)return dp[x + y - 1][tar];
    int ans = 0;
    if (P[x] == S[x + y - 1] && x <= n) {
        ans += dfs(x + 1, y, 0);
        ans %= mod;
    }
    if (Q[y] == S[x + y - 1] && y <= n) {
        ans += dfs(x, y + 1, 1);
        ans %= mod;
    }   
    return dp[x + y - 1][tar] = ans;
}

void slove() {
    cin >> n;
    for (int i = 0; i <= 2 * n + 5; i++)dp[i][0] = dp[i][1] = -1;
    for (int i = 1; i <= n; i++)cin >> P[i];
    for (int i = 1; i <= n; i++)cin >> Q[i];
    for (int i = 1; i <= 2 * n; i++)cin >> S[i];
    int ans = 0;
    if (P[1] == S[1])ans += dfs(2, 1, 0), ans %= mod;
    if (Q[1] == S[1])ans += dfs(1, 2, 1), ans %= mod;
    cout << ans << endl;
}

杭电多校第二场:E Slayers Come(线段树并查集优化)

题意:

给定一个长度为 n n n的数组,每个位置有一个怪兽,有血量 b [ i ] b[i] b[i]和攻击力 a [ i ] a[i] a[i]。有 m m m个技能,每个技能有三个参数: V , L , R V,L,R V,L,R:可以杀死 V V V位置的怪物,这个怪物死后会攻击两边的怪,对左边造成 a [ i ] − L a[i]-L a[i]L伤害,右边造成 a [ i ] − R a[i]-R a[i]R的伤害。求如何组合技能使每个怪都被至少杀一次的方案。

思路:

首先可以发现每个技能是可以杀死一段区间内的怪物,那么我们如果能处理出来所有技能的区间 [ L , R ] [L,R] [L,R],那么问题就转化为从这些技能中选取若干使得区间 [ 1 , n ] [1,n] [1,n]被覆盖,就是区间完全覆盖问题,看数据范围,很容易想到线段树优化,经典题啊!那么我们就该考虑如何快速求出每个技能的区间。依题意如果 a [ i ] − r [ k ] ≥ b [ i + 1 ] a[i]-r[k] \geq b[i+1] a[i]r[k]b[i+1],那么我们就可以通过传递杀死它,那么我们把 a [ i ] − b [ i + 1 ] a[i]-b[i+1] a[i]b[i+1]按从大到小排序,把每个技能的 r r r从小到大排序,优先处理容易死的怪,如果满足上述关系,那么就把 [ i , i + 1 ] [i,i+1] [i,i+1]合并到一个连通块里,用并查集优化。左端点同理。
那么接下来解决如何组合技能是区间被重复覆盖。
对于当前区间 [ L , R ] [L,R] [L,R],
d p [ r ] + = ∑ j = L − 1 R d p [ j ] dp[r]+=\sum_{j=L-1}^{R}{dp[j]} dp[r]+=j=L1Rdp[j] :选这个区间,那么左端点接在 [ L − 1 , R ] [L-1,R] [L1,R]的位置,都可以使 [ 1 , R ] [1,R] [1,R]被覆盖。
0 ≤ j ≤ L − 2 , d p [ j ] = d p [ j ] ∗ 2 0\leq j \leq L-2,dp[j] =dp[j]*2 0jL2,dp[j]=dp[j]2:选不选这个区间,区间 [ 0 , L − 2 ] [0,L-2] [0,L2]还是会被覆盖,因为题目问的是如何组合技能
优化用线段树即可。

code:

来自大佬的代码

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
#define int long long
typedef pair<int, int> PII;
const int N = 2e5 + 10, mod = 998244353;
int n, m, a[N], b[N];
struct Node
{
    int v, l, r;
    int ll, rr;
}sk[N];
struct Tree
{
    int l, r;
    int sum;
    int tag;
}tr[N << 2];
int p[N];
PII d[N];

int find(int x)  // 并查集
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

void pushup(int u)
{
    tr[u].sum = (tr[u << 1].sum + tr[u << 1 | 1].sum) % mod;
}

void pushdown(int u)
{
    auto &root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
    if(root.tag > 1)
    {
        left.sum = left.sum * root.tag % mod, right.sum = right.sum * root.tag % mod;
        left.tag = left.tag * root.tag % mod, right.tag = right.tag * root.tag % mod;
        root.tag = 1;
    }
}

void build(int u, int l, int r)
{
    tr[u] = {l, r, 0, 1};
    if(l == r) return ;
    else
    {
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}

void add(int u, int x, int v)
{
    if(x == tr[u].l && x == tr[u].r) tr[u].sum = (tr[u].sum + v) % mod;
    else 
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if(x <= mid) add(u << 1, x, v);
        if(x > mid) add(u << 1 | 1, x, v);
        pushup(u);
    }
}

void mul(int u, int l, int r)
{
    if(l <= tr[u].l && r >= tr[u].r) 
    {
        tr[u].sum = tr[u].sum * 2 % mod;
        tr[u].tag = tr[u].tag * 2 % mod;
    }
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if(l <= mid) mul(u << 1, l, r);
        if(r > mid) mul(u << 1 | 1, l, r);
        pushup(u);
    }
}

int query(int u, int l, int r)
{
    if(l <= tr[u].l && r >= tr[u].r) return tr[u].sum;
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        int res = 0;
        if(l <= mid) res = (res + query(u << 1, l, r)) % mod;
        if(r > mid) res = (res + query(u << 1 | 1, l, r)) % mod;
        pushup(u);
        return res % mod;
    }
}

void solve()
{
    cin >> n >> m;
    for(int i = 1 ; i <= n ; i ++ ) cin >> a[i] >> b[i];
    for(int i = 1 ; i <= m ; i ++ )
    {
        int v, l, r;
        cin >> v >> l >> r;
        sk[i] = {v, l, r};
    }
    for(int i = 1 ; i <= n ; i ++ ) p[i] = i;
    for(int i = 1 ; i < n ; i ++ ) d[i] = {a[i] - b[i + 1], i};
    sort(d + 1, d + n);
    sort(sk + 1, sk + m + 1, [](Node &a, Node &b){
        return a.r > b.r;
    });
    int cur = n - 1;
    for(int i = 1 ; i <= m ; i ++ )
    {
        while(cur >= 1 && d[cur].first >= sk[i].r)
        {
            int pos = d[cur].second;
            p[pos] = pos + 1;
            cur -- ;
        }
        sk[i].rr = find(sk[i].v);
    }
        
    
    for(int i = 1 ; i <= n ; i ++ ) p[i] = i;
    for(int i = 1 ; i < n ; i ++ ) d[i] = {a[i + 1] - b[i] , i};
    sort(d + 1, d + n);
    sort(sk + 1, sk + m + 1, [](Node &a, Node &b){
        return a.l > b.l;
    });
    cur = n - 1;
    for(int i = 1 ; i <= m ; i ++ )
    {
        while(cur >= 1 && d[cur].first >= sk[i].l)
        {
            int pos = d[cur].second;
            p[pos + 1] = pos;
            cur --;
        }
        sk[i].ll = find(sk[i].v);
    }
    sort(sk + 1, sk + m + 1, [](Node &a, Node &b){
        return a.rr < b.rr;
    });

    build(1, 0, n);
    add(1, 0, 1);
    for(int i = 1 ; i <= m ; i ++ )
    {
        int l = sk[i].ll , r = sk[i].rr;
        add(1, r, query(1, l - 1, r));
        if(l >= 2) mul(1, 0, l - 2);
    }
    cout << query(1, n, n) << endl;
}

signed main()
{
    ios::sync_with_stdio(0),cin.tie(0);
    int T = 1;
    cin >> T;
    while(T -- ) solve();
    return 0;
}

Pawn(背包+记录路径)

思路:

首先 f [ i ] [ j ] [ w ] f[i][j][w] f[i][j][w]表示从底部走到 ( i , j ) (i,j) (i,j)且当前所有豆子总数 m o d ( k + 1 ) = w mod(k+1)=w mod(k+1)=w的最大豆子数。
那么转移我们就直接枚举是从下一层哪个方向走过来的即可,顺便记录一下路径。

code:

template<int m>
struct modint {
	unsigned int x;
	constexpr modint()noexcept:x(){}
	template<typename T>
	constexpr modint(T x_)noexcept:x((x_%=m)<0?x_+m:x_){}
	constexpr unsigned int val()const noexcept{return x;}
	constexpr modint&operator++()noexcept{if(++x==m)x=0;return*this;}
	constexpr modint&operator--()noexcept{if(x==0)x=m;--x;return*this;}
	constexpr modint operator++(int)noexcept{modint res=*this;++*this;return res;}
	constexpr modint operator--(int)noexcept{modint res=*this;--*this;return res;}
	constexpr modint&operator+=(const modint&a)noexcept{x+=a.x;if(x>=m)x-=m;return*this;}
	constexpr modint&operator-=(const modint&a)noexcept{if(x<a.x)x+=m;x-=a.x;return*this;}
	constexpr modint&operator*=(const modint&a)noexcept{x=(unsigned long long)x*a.x%m;return*this;}
	constexpr modint&operator/=(const modint&a)noexcept{return*this*=a.inv();}
	constexpr modint operator+()const noexcept{return*this;}
	constexpr modint operator-()const noexcept{return modint()-*this;}
	constexpr modint pow(long long n)const noexcept {
		if(n<0)return pow(-n).inv();
		modint x=*this,r=1;
		for(;n;x*=x,n>>=1)if(n&1)r*=x;
		return r;
	}
	constexpr modint inv()const noexcept {
		int s=x,t=m,x=1,u=0;
		while(t)
		{
			int k=s/t;
			s-=k*t;
			swap(s,t);
			x-=k*u;
			swap(x,u);
		}
		return modint(x);
	}
	friend constexpr modint operator+(const modint&a,const modint&b){return modint(a)+=b;}
	friend constexpr modint operator-(const modint&a,const modint&b){return modint(a)-=b;}
	friend constexpr modint operator*(const modint&a,const modint&b){return modint(a)*=b;}
	friend constexpr modint operator/(const modint&a,const modint&b){return modint(a)/=b;}
	friend constexpr bool operator==(const modint&a,const modint&b){return a.x==b.x;}
	friend constexpr bool operator!=(const modint&a,const modint&b){return a.x!=b.x;}
	friend ostream&operator<<(ostream&os,const modint&a){return os<<a.x;}
	friend istream&operator>>(istream&is,modint&a){long long v;is>>v;a=modint(v);return is;}
};
using mint = modint<1000000007>;
// using mint = modint<998244353>;
namespace Combine{
	const int Combine_max = 1e5 + 50;
	mint fac[Combine_max];
	void init() { fac[0] = 1; for (int i = 1; i < Combine_max; ++ i) fac[i] = fac[i - 1] * i; }
	mint A(int n, int m) { return fac[n] / fac[n - m]; }
	mint C(int n, int m) { return fac[n] / (fac[n - m] * fac[m]); }
	mint ksm(mint x, int exp){
		mint res = 1;
		for (; exp; x *= x, exp >>= 1) if (exp & 1) res *= x;
		return res;
	}
}
using namespace Combine;
 
int f[111][111][11]; //f[i][j][w]走到(i,j),价值mod(k+1)=w的最大价值
int n, m, a[111][111];
pii road[111][111][11];
signed main() {
#ifdef JANGYI
    freopen("input.in", "r", stdin);
    freopen("out.out", "w", stdout);
    auto now = clock();
#endif  
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int k; 
    cin >> n >> m >> k;
    k++;
    memset(f, -1, sizeof f);
    for(int i = 1; i <= n; i++) {
        string s; cin >> s;
        for(int j = 1; j <= m; j++)
            a[i][j] = s[j - 1] - '0';
    }
    for(int i = n; i >= 1; i--) {
        for(int j = 1; j <= m; j++) {
            if(i == n) f[n][j][a[i][j] % k] = a[i][j];
            else {
                for(int w = 0; w < k; w++) { 
                    if(j > 1 && f[i + 1][j - 1][(w - a[i][j] % k + k) % k] != -1 && f[i + 1][j - 1][(w - a[i][j] % k + k) % k] + a[i][j] > f[i][j][w]) {
                        f[i][j][w] = f[i + 1][j - 1][(w - a[i][j] % k + k) % k] + a[i][j];
                        road[i][j][w] = {j - 1, (w - a[i][j] % k + k) % k};
                    } 
                    if(j < m && f[i + 1][j + 1][(w - a[i][j] % k + k) % k] != -1 && f[i + 1][j + 1][(w - a[i][j] % k + k) % k] + a[i][j] > f[i][j][w]) {
                        f[i][j][w] = f[i + 1][j + 1][(w - a[i][j] % k + k) % k] + a[i][j];
                        road[i][j][w] = {j + 1, (w - a[i][j] % k + k) % k};
                    }
                }
            }
        }
    }
    int ans = -1, id;
    for(int i = 1; i <= m; i++) {
        if(f[1][i][0] > ans) {
            ans = f[1][i][0];
            id = i;
        }
    }
    // D(id)
    if(ans == -1) {
        cout << -1 << '\n';
        return 0;
    }
    cout << ans << '\n';
    int i = 1, j = id, w = 0;
    vector<string> pos;
    while(i != n) {
        pii now = road[i][j][w];
        if(now.fi == j - 1) pos.pb("R");
        else pos.pb("L");
        // DD(i, j)
        i++;
        j = now.fi; w = now.se;
    }
    cout << j << '\n';
    reverse(all(pos));
    for(auto t : pos) cout << t;
#ifdef JANGYI
    cerr << "================================" << endl;
    cerr << "Program run for " << (clock() - now) / (double)CLOCKS_PER_SEC * 1000 << " ms." << endl;
#endif
    return 0;
}   

Slash(线性dp)

题意:

给一个字符矩阵和一个字符串 s s s,每一次只能向右或向下走,问走过的路径上的字符连起来最多有多少个不相交的连续子串是 s s s。比如: a b c d a b c abcdabc abcdabc中有两个 a b c abc abc

思路:

朴素的线性 d p dp dp,我们令 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示走到了 ( i , j ) (i,j) (i,j)且当前已经最新匹配到 s s s的第 k k k个字符的最大数量。比如: a b c d e a b c , s = a b c abcdeabc,s=abc abcdeabcs=abc,那么 d p [ 1 ] [ 3 ] [ 0 ] = 1 , d p [ 1 ] [ 6 ] [ 1 ] = 1 , d p [ 1 ] [ 8 ] [ 0 ] = 2 dp[1][3][0]=1,dp[1][6][1]=1,dp[1][8][0]=2 dp[1][3][0]=1dp[1][6][1]=1,dp[1][8][0]=2。那么其中是有一些不合法的状态,为了不让其影响我们正确状态的更新,我们把它们赋值成很小的数即可。那么我们应考虑如何转移 d p [ i ] [ j ] [ 0 ] dp[i][j][0] dp[i][j][0]这个状态。 d p [ i ] [ j ] [ 0 ] dp[i][j][0] dp[i][j][0]代表这个位置和 s s s串失配了,那么它就可以由上一步的每个状态转移过来。

code:

string s, str[111];
int f[111][111][111];
signed main() {
#ifdef JANGYI
    freopen("input.in", "r", stdin);
    freopen("out.out", "w", stdout);
    auto now = clock();
#endif  
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, m;
	cin >> n >> m;
	cin >> s;
	int len = s.size();
	s = ' ' + s;
	for(int i = 1; i <= n; i++) cin >> str[i], str[i] = ' ' + str[i];
	memset(f, -0x3f, sizeof f);
	f[0][1][0] = 0;
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= m; j++) {
			for(int k = 1; k <= len; k++) {
				if(str[i][j] == s[k]) 
					f[i][j][k] = max(f[i - 1][j][k - 1], f[i][j - 1][k - 1]);
			}
			f[i][j][0] = f[i][j][len] + 1;
			for(int k = 0; k <= len; k++)
				f[i][j][0] = max(f[i][j][0], max(f[i][j - 1][k], f[i - 1][j][k]));
		}
	}
	int ans = 0;
	for(int i = 0; i <= len; i++) ans = max(ans, f[n][m][i]);
	cout << ans << endl;
#ifdef JANGYI
    cerr << "================================" << endl;
    cerr << "Program run for " << (clock() - now) / (double)CLOCKS_PER_SEC * 1000 << " ms." << endl;
#endif
    return 0;
}   
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值