B 洛谷 P3604 美好的每一天 [莫队算法]

本文针对一个特定算法问题“回归天空”进行了深入分析,提出了一种O(n²)的初步解决方案,并进一步探讨了如何通过莫队算法优化到更高效的O(n√n)复杂度。文章详细解释了算法思路和实现细节。

题目背景

时间限制3s,空间限制162MB

素晴らしき日々

我们的情人,不过是随便借个名字,用幻想吹出来的肥皂泡,把信拿去吧,你可以使假戏成真。我本来是无病呻吟,漫无目的的吐露爱情---现在这些漂泊不定的鸟儿有地方栖息了,你可以从信里看出来。拿去吧---由于不是出自真心,话就说得格外动听,拿去吧,就这么办吧...

由于世界会在7月20日完结,作为救世主,间宫卓司要在19日让所有人回归天空

现在已经是19日傍晚,大家集合在C栋的天台上,一共n个人

在他们面前,便是终之空,那终结的天空

题目描述

回归天空是一件庄重的事情,所以卓司决定让大家分批次进行,给每个人给了一个小写字母'a'->'z'作为编号

一个区间的人如果满足他们的编号重排之后可以成为一个回文串,则他们可以一起回归天空,即这个区间可以回归天空

由于卓司是一个喜欢妄想的人,他妄想了m个区间,每次他想知道每个区间中有多少个子区间可以回归天空

因为世界末日要来了,所以卓司的信徒很多

输入输出格式

输入格式:

 

第一行两个数n,m

之后一行一个长为n的字符串,代表每个人的编号

之后m行每行两个数l,r代表每次卓司妄想的区间

输出格式:

m行,每行一个数表示答案

输入输出样例

输入样例#1:
6 6
zzqzzq
1 6
2 4
3 4
2 3
4 5
1 1
输出样例#1:
16
4
2
2
3
1

说明

对于10%的数据,n,m<=100

对于30%的数据,n,m<=2000

对于100%的数据,n,m<=60000

字符集大小有梯度

在大家回归天空之后,彩名露出了阴冷的笑容


先奉上O(n2) 30分骗分

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
const int N=2005;
inline int read(){
    char c=getchar();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();}
    return x*f;
}
int n,Q,l,r;
char c[N];
int a[N][N],s[N][N],cnt[300],now;
void ini(){
    for(int i=1;i<=n;i++){
        memset(cnt,0,sizeof(cnt));now=0;
        for(int j=i;j<=n;j++){
            if(cnt[c[j]]%2==0) now++;
            else now--;
            cnt[c[j]]++;
            if((j-i+1)%2==0&&now==0) a[i][j]=1;
            if((j-i+1)%2==1&&now==1) a[i][j]=1;
            s[i][j]=s[i][j-1]+a[i][j];
            //printf("hi %d %d %d  %d\n",i,j,a[i][j],s[i][j]);
        }
    }
}
void solve(int l,int r){
    int ans=0;
    for(int i=l;i<=r;i++) ans+=s[i][r]-s[i][i-1];
    printf("%d\n",ans);
}
int main(int argc, const char * argv[]) {
    n=read();Q=read();
    scanf("%s",c+1);
    ini();
    while(Q--){
        l=read();r=read();
        solve(l,r);
    }
    return 0;
}
View Code

 

标解:

一个区间可以重排成为回文串,即区间中最多有一个字母出现奇数次,其他的都出现偶数次

发现这个和  类似

这样如果一个区间的  和为  或者  ,则这个区间可以重排成为回文串,即回归天空

把每个位置的值变为前缀  和,那么区间  可以回归天空当且仅当  为  或者 

 即  的异或和

这样用莫队算法,可以做到  的复杂度

怎么用莫队呢?去请教了__stdcall

考虑[l,r]—>[l,r+1],就是要找出这个区间里有几个前缀xor满足 a[r+1]^它 =0或(1<<x)

那么用一个桶c存起来 更新答案加上c[a[r+1]^(1<<x)]和c[a[r+1]]就行了

有个细节,[l,r]对应的桶中应该是[l-1,r]的a

然后内存原因c用short

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long ll;
const int N=6e4+5,M=(1<<26)+5;
inline int read(){
    char c=getchar();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();}
    return x*f;
}
int n,Q,a[N],bl,pos[N];
char s[N];
struct ques{
    int l,r,id;
    bool operator <(const ques &a)const{
        return pos[l]==pos[a.l]?r<a.r:pos[l]<pos[a.l];
    }
}q[N];
unsigned short c[M];
int ans,anss[N];
inline void add(int x){//printf("add %d %d\n",x,a[x]);
    ans+=c[a[x]];
    for(int i=0;i<26;i++) ans+=c[a[x]^(1<<i)];
    c[a[x]]++;
}
inline void del(int x){
    c[a[x]]--;
    ans-=c[a[x]];
    for(int i=0;i<26;i++) ans-=c[a[x]^(1<<i)];
}
void solve(){
    sort(q+1,q+1+Q);
    c[0]++;
    int l=1,r=0;
    for(int i=1;i<=Q;i++){//printf("Q %d %d %d\n",q[i].l,q[i].r,q[i].id);
        while(r<q[i].r) r++,add(r);//printf("hi %d %d %d\n",l,r,ans);
        while(r>q[i].r) del(r),r--;//printf("hi %d %d %d\n",l,r,ans);
        while(l<q[i].l) del(l-1),l++;//printf("hi %d %d %d\n",l,r,ans);
        while(l>q[i].l) l--,add(l-1);//printf("hi %d %d %d\n",l,r,ans);
        anss[q[i].id]=ans;
    }
    for(int i=1;i<=Q;i++) printf("%d\n",anss[i]);
}
int main(int argc, const char * argv[]) {
    n=read();Q=read();
    scanf("%s",s+1);
    bl=sqrt(n);
    for(int i=1;i<=n;i++) a[i]=(1<<(s[i]-'a'))^a[i-1],pos[i]=(i-1)/bl+1;
    for(int i=1;i<=Q;i++) q[i].l=read(),q[i].r=read(),q[i].id=i;
    solve();
    return 0;
}

怎么卡常也还是70分

//
//  main.cpp
//  BB
//
//  Created by Candy on 2017/2/2.
//  Copyright © 2017年 Candy. All rights reserved.
//
#pragma GCC optimize("O2")

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
typedef long long ll;
const int N=6e4+5,M=(1<<26)+5;
inline int read(){
    char c=getchar();register int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();}
    return x*f;
}
int n,Q,a[N],bl,pos[N],bit[27];
char s[N];
struct ques{
    int l,r,id;
    bool operator <(const ques &a)const{
        return pos[l]==pos[a.l]?r<a.r:pos[l]<pos[a.l];
    }
}q[N];
unsigned short c[M];
int ans,anss[N];
inline void add(int x){//printf("add %d %d\n",x,a[x]);
    ans+=c[a[x]]++;
    for(register int i=0;i<26;i++) ans+=c[a[x]^bit[i]];
}
inline void del(int x){
    ans-=--c[a[x]];
    for(register int i=0;i<26;i++) ans-=c[a[x]^bit[i]];
}
inline void solve(){
    for(int i=0;i<26;i++) bit[i]=1<<i;
    sort(q+1,q+1+Q);
    c[0]++;
    int l=1,r=0;
    for(int i=1;i<=Q;i++){//printf("Q %d %d %d\n",q[i].l,q[i].r,q[i].id);
        while(r<q[i].r) add(++r);//printf("hi %d %d %d\n",l,r,ans);
        while(r>q[i].r) del(r--);//printf("hi %d %d %d\n",l,r,ans);
        while(l<q[i].l) del(l-1),l++;//printf("hi %d %d %d\n",l,r,ans);
        while(l>q[i].l) add(--l-1);//printf("hi %d %d %d\n",l,r,ans);
        anss[q[i].id]=ans;
    }
    for(int i=1;i<=Q;i++) printf("%d\n",anss[i]);
}
int main(int argc, const char * argv[]) {
    n=read();Q=read();
    scanf("%s",s+1);
    bl=sqrt(n);
    for(int i=1;i<=n;i++) a[i]=(1<<(s[i]-'a'))^a[i-1],pos[i]=(i-1)/bl+1;
    for(int i=1;i<=Q;i++) q[i].l=read(),q[i].r=read(),q[i].id=i;
    solve();
    return 0;
}
卡常后很丑

 

转载于:https://www.cnblogs.com/candy99/p/6361241.html

### P1816 题目解析 #### 问题描述 P1816《忠诚》的题目背景设定在一个庄园中,老管家为财主工作了十年。为了验证管家的忠诚度,财主设计了一种特殊的测试方法。具体来说,财主要求管家记录每日账目,并对其进行查询操作。 输入数据分为两部分: - **账目记录**:每一天有若干次账目记录,每条记录有一个具体的数值。 - **查询请求**:每次查询指定一段区间 `[a, b]`,询问该区间的最小值。 目标是实现一个程序,在满足高效性和准确性的同时处理这些查询请求[^4]。 --- #### 解决方案分析 此题的核心在于如何快速响应大量的区间最小值查询。以下是两种常见的解决思路: ##### 方法一:暴力枚举法 对于每一个查询 `(a, b)`,可以直接遍历数组中的子区间 `[a, b]` 来找到其中的最小值。然而,这种方法的时间复杂度较高,达到 \(O(Q \times N)\),其中 \(Q\) 是查询次数,\(N\) 是账目的总数。当数据规模较大时,这种算法可能无法通过所有测试用例。 ```cpp #include <bits/stdc++.h> using namespace std; int main() { int n, q; cin >> n >> q; // 账目数量和查询次数 vector<int> accounts(n); for (int i = 0; i < n; ++i) { cin >> accounts[i]; } while (q--) { int a, b; cin >> a >> b; // 查询范围 [a, b] int min_val = INT_MAX; for (int i = a - 1; i <= b - 1; ++i) { // 注意索引偏移 if (accounts[i] < min_val) { min_val = accounts[i]; } } cout << min_val << endl; } } ``` 虽然简单易懂,但在大规模数据下效率低下[^4]。 --- ##### 方法二:预处理 + 倍增 RMQ(Range Minimum Query) 倍增 RMQ 是一种高效的解决方案,其核心思想是对原数组进行预处理,从而加速后续的查询过程。具体步骤如下: 1. 定义 `f[i][j]` 表示从位置 `i` 开始,长度为 \(2^j\) 的子区间的最小值。 2. 利用动态规划的思想计算所有的 `f[i][j]` 值: - 当 \(j = 0\) 时,`f[i][0] = nums[i]`; - 对于更大的 \(j\),可以由两个更小区间的结果合并得到: \[ f[i][j] = \min(f[i][j-1], f[i + 2^{j-1}][j-1]) \] 3. 在查询阶段,给定区间 `[a, b]`,可以通过分解成两个重叠的部分来快速得出答案。假设区间长度为 \(len = b - a + 1\),则令 \(k = \lfloor\log_2(len)\rfloor\),最终结果为: \[ \text{result} = \min(f[a][k], f[b - 2^k + 1][k]) \] 下面是完整的代码实现: ```cpp #include <bits/stdc++.h> using namespace std; const int MAX_N = 5e4 + 5; const int LOG = 17; // log2(50000) // 动态规划表 int f[MAX_N][LOG]; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, m; cin >> n >> m; // 数组大小和查询次数 vector<int> nums(n); for (int i = 0; i < n; ++i) { cin >> nums[i]; // 输入账目数据 } // 初始化 dp 表 for (int i = 0; i < n; ++i) { f[i][0] = nums[i]; } // 计算更高层次的状态转移 for (int j = 1; j < LOG; ++j) { for (int i = 0; i + (1 << j) - 1 < n; ++i) { f[i][j] = min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]); } } // 处理查询 while (m--) { int l, r; cin >> l >> r; // 查询范围 [l, r] --l; --r; // 转换为零基索引 int k = floor(log2(r - l + 1)); cout << min(f[l][k], f[r - (1 << k) + 1][k]) << "\n"; } } ``` 这段代码利用倍增技术实现了高效的区间最值查询,时间复杂度降到了 \(O(N \log N + Q \log N)\)[^4]。 --- #### 总结 针对 P1816,《忠诚》这道题目提供了多种解法。如果追求简洁性,可以选择暴力枚举;但如果希望优化性能,则推荐采用倍增 RMQ 技术。后者不仅能够显著提升运行速度,还具有较高的通用性,适用于其他类似的区间查询场景。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值