2024.8 - 做题记录与方法总结 - Part 2

2024/08/07

mx模拟赛

A.简单移动
题目描述

给两个长度相等的字符串 A,B ,每次可以在 A 中任选一个字符并移到串的开头。求将 A 变成 B 的最小操作次数(无解输出 -1 )。

输入格式

输入两行两个字符串。保证长度相等。

输出格式

输出一行一个正整数表示答案。

样例
样例 1 输入
BABACBACBA
BABCABCAAB
样例 1 输出
7
数据范围与提示

N |A| 的长度。

对于 60\% 的数据, N\leq 3000

1\leq N\leq 10^7

A,B 只包含大写字母。

首先不难发现的是,只要字母出现次数对的上,我们必然有一种方法重排(就是假设全部重来,把所有字母都按顺序放到一个空字符串中)

什么样的字母不需要重排呢?

我们发现,我们每拿一个字符,就有一坨字符从前面掉到后面,那么我们就可以通过这个过程来省事

那么我们用双指针,对 B B B 的后缀和 A A A 的从后往前的子序列 进行最大匹配,就可以求出不用动的字符数

最后用长度一减就完成了

O ( n ) O(n) O(n)

Ac-code:

#include<bits/stdc++.h>
using namespace std;

string a,b;
int t[30];

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);cout.tie(nullptr);
    freopen("move.in","r",stdin);
    freopen("move.out","w",stdout);
    cin>>a>>b;
    if(a.size() != b.size()) {puts("-1");return 0;}
    for(int i = 0;i<a.size();i++) t[a[i] - 'A']++;
    for(int i = 0;i<b.size();i++) t[b[i] - 'A']--;
    int len = a.size();
    for(int i = 0;i<30;i++) if(t[i]) {puts("-1");return 0;}
    int i = len - 1,j = len - 1,ans = 0;
    for(;i >= 0 && j >= 0;i--)
    	if(a[i] == b[j])
    		j--,ans++;
    cout<<len - ans;
    
	return 0;
}
B.星天花雨
题目描述

小 N 家的花园可以被看做一个 n\times m 的网格图,从上到下依次为第 1 行至第 n 行,从左到右分别为第 1 列至第 m 列。

现在小 N 要在花园的某些位置种上花,具体来说,可以用 c_{i,j} 来表示花园的第 i 行第 j 列处是否种有花, c_{i,j}=0 表示这个位置没有种花, c_{i,j}=1 则表示这个位置种有一朵花。

小 N 找出了两个长度分别为 n m 01 序列 a_{1\cdots n},b_{1\cdots m} ,那么她会在花园的第 i 行第 j 列种一朵花,当且仅当 a_i=b_j=1 。即

c_{i,j}=a_i\times b_j

在种完这些花之后,小 N 想要在她的花园中选出一个子矩形,使得这片子矩形中恰好种有 k 朵花。具体来说,设她选出的矩形的左上角是 (s_1,t_1) ,右下角是 (s_2,t_2) ,那么这个矩形需要满足:

  • 1\le s_1\le s_2\le n,1\le t_1\le t_2\le m

  • 恰好有 k 个位置 (x,y) 满足 c_{x,y}=1 ,且 s_1\le x\le s_2,t_1\le y\le t_2

她想知道有多少种这样的子矩形。由于这样的子矩形可能很多,你只需要输出答案对 998244353 取模的值即可。

输入格式

第一行三个整数 n,m,k

第二行 n 个正整数 a_1,a_2,\cdots,a_n

第三行 m 个正整数 b_1,b_2,\cdots,b_m

输出格式

输出一行一个整数表示符合条件的子矩形个数。

样例
样例 1 输入
3 4 4
0 1 1
1 1 0 1
样例 1 输出
6
样例 1 解释

小 N 的花园对应的 c 如下:

0000
1101
1101

合法的子矩形共有六种:

  • 左上角为 (1,1) ,右下角为 (3,2)

  • 左上角为 (1,1) ,右下角为 (3,3)

  • 左上角为 (2,1) ,右下角为 (3,2)

  • 左上角为 (2,1) ,右下角为 (3,3)

  • 左上角为 (1,2) ,右下角为 (3,4)

  • 左上角为 (2,2) ,右下角为 (3,4)

样例 2

该样例符合测试点 6\sim 8 的约束。

样例 3

该样例符合测试点 12 的约束。

样例 4

该样例符合测试点 17\sim 18 的约束。

数据范围与提示

对于所有测试点, 1\le n,m\le 10^5,1\le k\le 10^9 。每个测试点的具体限制见下表:

测试点编号 n m k 特殊性质
1-2 \leq 3 \leq 10
3-5 \leq 50 \leq 1000 A
6-8
9 \leq 500 \leq 10^4 A
10-11
12 \leq 1000 A
13-14
15-16 \leq 5 \leq 10^5 \leq 10^5
17-18 \leq 10^5
19-20 \leq 10^9

特殊性质 A: a_1=a_2=\cdots=a_n=1

这个奇怪的描述矩形的方式,一看就很奇怪,非常值得关注

这些 1 , 0 1,0 1,0 直接决定了一行,或一列的情况

我们再来考察 1 1 1 的分布

只有当 a l i n e , b c o l a_{line},b_{col} aline,bcol都是 1 1 1,这个位置才是 1 1 1

稍微画画图就知道,对于一个矩形有多少个 1 1 1,取决于 a a a 区间 1 1 1 的个数 × \times × b b b 区间 1 1 1 的个数

那么,我们只需要知道有多少 a , b a,b a,b 1 1 1 个数为 i i i 的区间数,相乘即可

那么,对于 A i × B j = k A_i \times B_j = k Ai×Bj=k A i , B j ∈ Z A_i,B_j \in Z Ai,BjZ

我们可以处理出每一个含 1 1 1 个数的区间数量,放在桶里

O ( n 2 ) O(n^2) O(n2) 可以过 80 80 80 分的点

显然这个地方可以优化————直接针对可能成为答案的区间,并用 O ( n ϕ ( n ) ) O(n\phi(n)) O(nϕ(n)) 求出来

什么样的可以成为答案?显然是 k k k 的因子

那么我们直接试除法,把每一个因子记录下来

最后针对每一个因子,求包含这么多 1 1 1 的区间
(这里不知道写没写唐,我是出来每一个 1 1 1 的 前缀 0 0 0 个数 + 1、后缀 0 0 0 + 1,然后对区间 1 1 1 相乘)

#include<bits/stdc++.h>
using namespace std;
#define int long long
int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

const int N = 1e5+5,mod = 998244353;

int fa[N],fb[N],p[N],top = -1,tp,nxt[N],lst[N];

signed main() {
	freopen("rain.in","r",stdin);
	freopen("rain.out","w",stdout);
    int n = rd(),m = rd(),k = rd();
    vector<int> a(n + 1),b(m + 1);
    for(int i = 1;i<=n;i++) a[i] = rd();
    for(int i = 1;i<=m;i++) b[i] = rd();
    for(int i = 1;i * i <= k;i++) {
        if(k % i == 0) {
            int j = k / i;
            p[++top] = i;
            if(i ^ j) p[++top] = j;
        }
    }

    for(int i = 1;i<=n;i++) {
        int j = i + 1;
        if(a[i]) nxt[++tp] = 1;
        if(a[i] && !a[j]){
            while(!a[j] && j <= n) j++;
            nxt[tp] = j - i;
            i = j - 1;
        }
    }
    int T = 0;
    for(int i = 1;i<=n;i++) {
        int j = i - 1;
        if(a[i]) lst[++T] = 1;
        if(a[i] && !a[j]) {
            while(!a[j] && j >= 1) j--;
            lst[T] = i - j;
            i = nxt[T] + i - 1;
        }
    }
    for(int i = 0;i<= top;i++) {
        for(int j = 1;j<=tp - p[i] + 1;j++) {
            int w = j + p[i] - 1;
            fa[i] = (fa[i] + lst[j] * nxt[w] % mod) % mod;
        }
    }

    tp = 0;
    for(int i = 1;i<=m;i++) {
        int j = i + 1;
        if(b[i]) nxt[++tp] = 1;
        if(b[i] && !b[j]){
            while(!b[j] && j <= m) j++;
            nxt[tp] = j - i;
            i = j - 1;
        }
    }
    T = 0;
    for(int i = 1;i<=m;i++) {
        int j = i - 1;
        if(b[i]) lst[++T] = 1;
        if(b[i] && !b[j]) {
            while(!b[j] && j >= 1) j--;
            lst[T] = i - j;
            i = nxt[T] + i - 1;
        }
    }
    for(int i = 0;i<= top;i++) {
        for(int j = 1;j<=tp - p[i] + 1;j++) {
            int w = j + p[i] - 1;
            fb[i] = (fb[i] + lst[j] * nxt[w] % mod) % mod;
        }
    }
    int ans = 0;
    for(int i = 0;i<=top;i += 2) {
        if(i == top) ans = (ans + fa[i] * fb[i] % mod) % mod;
        else {
            ans = (ans + fa[i] * fb[i ^ 1] % mod) % mod;
            ans = (ans + fa[i ^ 1] * fb[i] % mod) % mod;
        }
    }
    wt(ans % mod);

	return 0;
}
  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值