Codeforces Round #441 Div. 2 E,F

本文介绍了一种利用2-SAT思想和Tarjan算法解决字符串字典序比较问题的方法,并提出一种高效算法来统计满足特定条件的区间数量。

E:
传送门

题意:
n 个由1~ m 数字组成的字符串(n,m1e5),每个字符串长度为 li(ili1e5) 。字典序大小满足小写比大写都大。现在可以改变任意数字的大小写,满足所有前面的串字典序小等于后面的串。

题解:
首先考虑怎么满足一个串比后面所有的都要小?是一个一个判断,还是有别的方法。

其实只要保证该串小等于后面的一个串就好了。

下面考虑怎么满足小等于后面的一个串。

设前面的串为 s1 ,后面的串为 s2

如果 s1 s2 的前缀,那么显然无论如何改变大小写都无法使得这两个串大小关系改变,直接跳过。
同理若 s2 s1 的前缀,那么直接输出”No”。

不然设两串第一个不同的位置为 k 。考虑两种不同的情况。
1.s1[k]<s2[k],显然此时若 s2[k] 是大写,那么 s1[k] 是大写, s1[k] 是大写,那么 s2[k] 是小写。
2. s1[k]>s2[k] ,此时 s1[k] 必定是大写, s2[k] 必定是小写。

发现上面的思想就是 2SAT 思想,用 Tarjan 方法可以 O(n) 求解。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
struct IO{
    inline int read(){
        char ch=getchar();int i=0,f=1;
        while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
        while(isdigit(ch)){i=(i<<1)+(i<<3)+ch-'0';ch=getchar();}
        return i*f;
    }
    inline void W(int x){
        static int buf[50];
        if(!x){putchar('0');return;}
        if(x<0){putchar('-');x=-x;}
        while(x){buf[++buf[0]]=x%10;x/=10;}
        while(buf[0])putchar(buf[buf[0]--]+'0');
    }
}io;

const int Maxn=1e5+50;
int n,m,dfn[Maxn<<1],low[Maxn<<1],st[Maxn<<1],top,tot,id[Maxn<<1],ind,ins[Maxn<<1];
vector<int>ch[Maxn];
vector<int>edge[Maxn<<1];
#define is(x) 2*x-1
#define isnot(x) 2*x
#define addedge(x,y) edge[x].push_back(y)
inline void dfs(int now){
    dfn[now]=low[now]=++ind;st[++top]=now;ins[now]=1;
    for(int e=edge[now].size()-1;e>=0;e--){
        int v=edge[now][e];
        if(dfn[v]){
            if(!ins[v])continue;
            low[now]=min(low[now],dfn[v]);
        }
        else dfs(v),low[now]=min(low[now],low[v]);
    }
    if(low[now]==dfn[now]){
        ++tot;
        while(low[st[top]]==dfn[now]){
            int u=st[top--];
            id[u]=tot;ins[u]=0;
        }
    }
}
int main(){
    n=io.read(),m=io.read();
    for(int i=1;i<=n;i++){
        int l=io.read();
        for(int j=1;j<=l;j++){
            ch[i].push_back(io.read());
        }
    }
    for(int i=2;i<=n;i++){
        int l1=ch[i-1].size(),l2=ch[i].size(),lim=min(l1,l2),pos=0;
        while(pos<lim&&ch[i-1][pos]==ch[i][pos])pos++;
        if(pos==lim){
            if(l1>l2){
                puts("No");return 0;
            }
        }else{
            if(ch[i-1][pos]<ch[i][pos]){
                addedge(is(ch[i-1][pos]),is(ch[i][pos]));
                addedge(isnot(ch[i][pos]),isnot(ch[i-1][pos]));
            }else{
                addedge(isnot(ch[i][pos]),is(ch[i][pos]));
                addedge(is(ch[i-1][pos]),isnot(ch[i-1][pos]));
            }
        }
    }
    for(int i=1;i<=m;i++){
        if(!dfn[is(i)])dfs(is(i));
        if(!dfn[isnot(i)])dfs(isnot(i));
    }
    static vector<int>ans;
    for(int i=1;i<=m;i++){
        if(id[is(i)]>id[isnot(i)])ans.push_back(i);
        else if(id[is(i)]==id[isnot(i)]){
            puts("No");return 0;
        }
    }
    puts("Yes");
    printf("%d\n",(int)ans.size());
    for(int e=0;e<ans.size();e++){
        printf("%d ",ans[e]);
    }
}

F:
传送门
题意:
求满足区间或大于区间最大值的区间个数。

题解:
考虑一个区间,只要这个区间的任意值非最大值有一位不与最大值相同,那么这个区间就是合法区间。

考虑找出所有值左右第一个大于它的位置,那么以它为最大值的区间就固定在这一段中。只要再找出这个区间中左右第一个有一位不与最大值相同的值的位置,那么这个位置左边的所有位置都可以与最大值右边的位置构成一个合法区间。右边也同理。

这样计算会有重复的部分,只需要减去两端算重的部分即可。

现在问题转化为找出每个值左右比他大的第一个位置,单调栈 O(n) 可以完成。

同时,要求出左右第一个与最大值不同的位置,只需要 O(nlogn) 求即可。

还有一种特殊的情况 {3,3,3,3} ,即相等的情况。考虑怎么去重。

其实只需要保证每个区间的最大值是最右边的那个值就行了。具体实现就是单调栈扫描一边满足 <ai ,一边满足 ai .

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
struct IO{
    inline int read(){
        char ch=getchar();int i=0,f=1;
        while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
        while(isdigit(ch)){i=(i<<1)+(i<<3)+ch-'0';ch=getchar();}
        return i*f;
    }
    inline void W(int x){
        static int buf[50];
        if(!x){putchar('0');return;}
        if(x<0){putchar('-');x=-x;}
        while(x){buf[++buf[0]]=x%10;x/=10;}
        while(buf[0])putchar(buf[buf[0]--]+'0');
    }
}io;
const int Maxn=2e5+50;
int n,a[Maxn],l[Maxn],r[Maxn],st[Maxn],pos[Maxn],top,diff_l[Maxn],diff_r[Maxn],mx[35];
int mxpos[35];

int main(){
    n=io.read(); 
    for(int i=1;i<=n;i++)a[i]=io.read();
    for(int i=1;i<=n;i++){
        while(top&&st[top]<=a[i])top--;
        l[i]=pos[top]+1;
        st[++top]=a[i];pos[top]=i;
    }
    top=0;pos[top]=n+1;
    for(int i=n;i>=1;i--){
        while(top&&st[top]<a[i])top--;
        r[i]=pos[top]-1;
        st[++top]=a[i];pos[top]=i;
    }
    for(int i=1;i<=n;i++){
        int p=0;
        for(int j=0;(1ll<<j)<=a[i];j++){
            if((1ll<<j)&a[i])mx[j]=max(mx[j],i);
            else p=max(p,mx[j]);
        }
        diff_l[i]=p;
    }
    fill(mx,mx+32+1,n+1);
    for(int i=n;i>=1;i--){
        int p=n+1;
        for(int j=0;(1ll<<j)<=a[i];j++){
            if((1ll<<j)&a[i])mx[j]=min(mx[j],i);
            else p=min(p,mx[j]);
        }
        diff_r[i]=p;
    }
    static long long ans=0;
    for(int i=1;i<=n;i++){
        if(diff_l[i]>=l[i]){
            ans+=1ll*(diff_l[i]-l[i]+1)*(r[i]-i+1);
        }
        if(diff_r[i]<=r[i]){
            ans+=1ll*(r[i]-diff_r[i]+1)*(i-l[i]+1);
        }
        if(diff_l[i]>=l[i]&&diff_r[i]<=r[i]){
            ans-=1ll*(r[i]-diff_r[i]+1)*(diff_l[i]-l[i]+1);
        }
    }
    cout<<ans<<endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值