洛谷试炼日记(manacher和trie树)(解题报告)

manacher

P4555 [国家集训队]最长双回文串

题目链接

思路:最开始只有一个n^2的思路,不用写都知道会T
看了大佬的思路之后恍然大悟
在求以位置i为中心的最大回文串长度时,更新以i + p[i] - 1位置结尾的最大回文串长度ll[i + p[i] - 1](ll[i + p[i] - 1] = max(ll[i + p[i] -1],p[i] - 1)
更新以i - p[i] +1位置开头的最大回文串长度rr[i - p[i] + 1] = max(rr[i - p[i] + 1],p[i] - 1)
由于只是求出了部分位置 在manacher结束之后 我们还需要更新一遍ll数组和rr数组
这时候的更新思路更为巧妙 rr[i] 通过顺序迭代更新
rr[i] = max(rr[i],rr[i - 2] - 2)
ll[i] 通过逆序迭代更新 ll[i] = max(ll[i],ll[i + 2] - 2)
具体实现见代码:
AC代码:

#include<cstdio>
#include<algorithm>
#include<cstring>

using namespace std;
const int N = 1e5 + 10;
char s[N << 1];
int p[N << 1],ll[N << 1],rr[N << 1];
int ans,cnt;
void read() {
    char c = getchar();
    s[++ cnt] = '~', s[++ cnt] = '|';
    while (c < 'a' || c > 'z') c = getchar();
    while (c >= 'a' && c <= 'z') s[++cnt] = c, s[++cnt] = '|', c = getchar();
    s[++ cnt] = '\0';
}
void manacher(){
    int mid = 0,r = 0;
    for(int i = 1;i <= cnt;i ++){
        if(i < r) p[i] = min(p[mid * 2 - i], r - i);
        while(s[i + p[i]] == s[i - p[i]]) p[i] ++;
        if(i + p[i] > r) r = i + p[i],mid = i;
        ll[i + p[i] - 1] = max(ll[i + p[i] - 1],p[i] - 1);
        rr[i - p[i] + 1] = max(rr[i - p[i] + 1],p[i] - 1);
    }
}
int main(){
    read();
    manacher();
    for(int i = 2;i <= cnt;i += 2) rr[i] = max(rr[i],rr[i - 2] - 2);
    for(int i = cnt;i >= 2;i -= 2) ll[i] = max(ll[i],ll[i + 2] - 2);
    for(int i = 2;i <= cnt;i += 2) if(rr[i] && ll[i]) ans = max(ans,ll[i] + rr[i]);
    printf("%d\n",ans);
    return 0;
}

P1659 [国家集训队]拉拉队排练

题目链接
这是居然会是一道蓝题呢!虽然说我一开始也没做出来
巧妙的用sum累计,从大到小遍历,后面小的数量中自然会有大的数量,妙啊
AC代码:

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

using namespace std;
const int N = 1e6 + 10;
const int mod = 19930726;
typedef long long ll;
int n;ll k;
char s[N << 1];
int p[N << 1];
int cnt;int tot[N];
ll ans = 1;
void read(){
    char c = getchar();
    s[0] = '~',s[cnt = 1] = '|';
    while(c < 'a' || c > 'z') c = getchar();
    while(c >= 'a' && c <= 'z') s[++ cnt] = c,s[++ cnt] = '|',c = getchar();
}
ll qpow(ll a,int b){
    ll ans = 1;
    while(b){
        if(b & 1) ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}
void manacher(){
    int mid = 0,r = 0;
    for(int i = 1;i <= cnt;i ++){
        if(i < r) p[i] = min(p[mid * 2 - i], r - i);
        while(s[i + p[i]] == s[i - p[i]]) p[i] ++;
        if(i + p[i] > r) r = i + p[i],mid = i;
        if((p[i] - 1) % 2) tot[p[i] - 1] ++;
    }
}
int main(){
    scanf("%d%lld",&n,&k);
    read();
    manacher();
    int sum = 0;
    for(int i = n;i >= 1;i --){
        if(i % 2 == 0) continue;
        sum += tot[i];
        if(k >= sum){
            ans = (ans * qpow(i,sum)) % mod;
            k -= sum;
        }
        else {
            ans = (ans * qpow(i,k)) % mod;
            k -= sum;
            break;
        }
    }
    if(k > 0) ans = -1;
    printf("%lld\n",ans);
    return 0;
}

Trie字典树

P3879 [TJOI2010]阅读理解

题目链接
就是一道Trie树板子题啦
因为数据加强过了所以用bitset避免MLE
AC代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<bitset>
#include<iostream>

using namespace std;
const int N = 1e4 + 10;
const int M = 5e5 + 10;
char s[N];
int ne[M][26],n,cnt = 0;
bitset<1001> vis[M];
void insert(int x){
    scanf("%s",s + 1);
    int u = 0;
    for(int i = 1;s[i];i ++){
        int v = s[i] - 'a';
        if(!ne[u][v]) ne[u][v] = ++cnt;
        u = ne[u][v];
    }
    vis[u][x] = 1;
}

void check(){
    scanf("%s",s + 1);
    int u = 0,flag = 1;
    for(int i = 1;s[i];i ++){
        int v = s[i] - 'a';
        if(!ne[u][v]){
            flag = 0;break;
        }
        u = ne[u][v];
    }
    if(flag){
        for(int i = 1;i <= n;i ++){
            if(vis[u][i]) printf("%d ",i);
        }
    }
    puts("");
}
int main(){
    scanf("%d",&n);
    for(int i = 1;i <= n;i ++){
        int x;scanf("%d",&x);
        for(int j = 0;j < x;j ++) insert(i);
    }
    int m;scanf("%d",&m);
    for(int i = 0;i < m;i ++) check();
    return 0;
}

P2292 [HNOI2004]L语言

题目链接
思路:还是建Trie树
然后用一个f数组标记一下匹配到的最远距离
AC代码:

#include<map>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<iostream>

using namespace std;
const int N = 1e6 + 10;
char s[N << 4];
int cnt,ch[1010][26];
int n,m;
bool flag[N];
map<string,bool> m1;
map<string,int> m2;

inline int read(){
    int ans = 0,f1 = 1;
    char c = getchar();
    while(c < '0' || c > '9'){
        if(c == '-') f1 = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9'){
        ans = ans * 10 + c - '0';
        c = getchar();
    }
    return ans * f1;
}

void insert(){
    scanf("%s",s + 1);
    int u = 0;
    for(int i = 1;s[i];i ++){
        int v = s[i] - 'a';
        if(!ch[u][v]) ch[u][v] = ++cnt;
        u = ch[u][v];
    }
    flag[u] = 1;
}
bool f[N << 1];
int query(){
    scanf("%s",s + 1);
    if(m1[s + 1])return m2[s + 1];
    memset(f,false,sizeof(f));
    f[0] = true;int ans;
    int len = strlen(s + 1);
    for(int i = 0;i <= len;i ++){
        if(!f[i]) continue;
        else ans = i;
        for(int j = i + 1,u = 0;s[j];j ++){
            int v = s[j] - 'a';
            u = ch[u][v];
            if(!u) break;
            if(flag[u]) f[j] = true;//更新最远距离
        }
    }
    m1[s + 1] = true;
    m2[s + 1] = ans ;
    return ans;
}

int main(){
    n = read(),m = read();
    for(int i = 0;i < n;i ++) insert();
    for(int i = 0;i < m;i ++) printf("%d\n",query()) ;
    return 0;
}

P2922 [USACO08DEC]Secret Message G

题目链接
这道题的思路真的超级的简单清晰
就是在查询的时候,如果查询串长度比字典树中所有串的长度都大,那就直接返回答案
如果字典树中有长度大于查询串的串,那么就把答案加上经过当前节点的串的数量减去以当前节点为结束字符的串的数量
也就是代码中ans + sum - bo
AC代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<queue>

using namespace std;
const int N = 1e4 + 10;
int s[N];
int m,n;
int ch[500010][2],cnt;
int sum[500010],bo[500010];
void insert(int len){
    int u = 0;
    for(int i = 0;i < len;i ++){
        if(ch[u][s[i]] == -1) ch[u][s[i]] = ++ cnt;
        u = ch[u][s[i]];
        sum[u] ++;
    }
    bo[u] ++;
}
int query(int len){
    int ans = 0;
    int u = 0;
    for(int i = 0;i < len;i ++){
        if(ch[u][s[i]] == -1) return ans;
        u = ch[u][s[i]];
        ans += bo[u];
    }
    return ans - bo[u] + sum[u];
}
int main(){
    scanf("%d%d",&m,&n);
    memset(ch,-1,sizeof(ch));
    for(int i = 0;i < m;i ++) {
        int x;
        scanf("%d", &x);
        for (int j = 0; j < x; j++) scanf("%d",&s[j]);
        insert(x);
    }
    for(int i = 0;i < n;i ++){
        int x;
        scanf("%d",&x);
        for(int j = 0;j < x;j ++)scanf("%d",&s[j]);
        printf("%d\n",query(x));
    }
    return 0;
}

P3065 [USACO12DEC]First! G

题目链接
思路:建立Trie树,然后假设某一串字典序最小,将它每一层的字符与同一层的字符建立有向边,可以得到一个有向图,再利用拓扑排序判断是否成环就行了
AC代码:
说实话拓扑排序的代码写起来挺有意思的

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>

using namespace std;
const int N = 3e4 + 10,M = 3e5 + 10;
int n,ans;
string s[N];
bool ok[N];
int tot,ch[M][26];
int in[26],e[26][26];
bool ed[M];
template <class T>
inline void read(T& res){
    res = 0;
    char c = getchar();
    int f = 1;
    while(c < '0' || c > '9'){
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9'){
        res = res * 10 + c - '0';
        c = getchar();
    }
    res *= f;
}
template<class T>
inline void write(T& x){
    if(x < 0){
        putchar('-');
        x = -x;
    }
    T y = 1;
    int len = 1;
    for(;y <= x / 10;y *= 10) len ++;
    for(;len;len --,x %= y,y /= 10) putchar(x / y + '0');
}
void insert(string x){
    int u = 0;
    for(int i = 0;x[i];i ++){
        int v = x[i] - 'a';
        if(!ch[u][v]) ch[u][v] = ++ tot;
        u = ch[u][v];
    }
    ed[u] = true;
}
queue<int> q;
void toposort(){
    while(!q.empty()) q.pop();
    for(int i = 0;i < 26;i ++)if(!in[i]) q.push(i);
    while(!q.empty()){
        int u = q.front();
        q.pop();
        for(int i = 0;i < 26;i ++){
            if(e[u][i]){
                -- in[i];
                if(!in[i]) q.push(i);
            }
        }
    }
}

bool check(string x){
    int u = 0;
    memset(e,0,sizeof(e));
    memset(in,0,sizeof(in));
    for(int i = 0;x[i];i ++){
        if(ed[u]) return false;
        int v = x[i] - 'a';
        for(int j = 0;j < 26;j ++){
            if(!e[v][j] && v != j && ch[u][j]){
                e[v][j] = 1;
                ++ in[j];
            }
        }
        u = ch[u][v];
    }
    toposort();
    for(int i = 0;i < 26;i ++) if(in[i]) return false;
    return true;
}
int main(){
    read(n);

    for(int i = 0;i < n;i ++){
        cin >> s[i];
        insert(s[i]);
    }
    for(int i = 0;i < n;i ++){
        if(check(s[i])){
            ans ++;
            ok[i] = 1;
        }
    }
    write(ans);
    putchar('\n');
    for(int i = 0;i < n;i ++){
        if(ok[i]) cout << s[i] << endl;
    }
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值