\(\cal T_1\) 路人女主

\(\mathbb{D}\rm escription\)

称一个长度为 \(2n+1\),字符集为 \(\{\text{A},\text{B},\text{C}\}\) 为好的当且仅当任意相邻两个字符不同。称两个长度为 \(2n+1\) 的好字符串对 \((s,t)\) 为势不两立的当且仅当它们的最长公共子序列长度恰为 \(n\)

给定两个包含 \(\text{A},\text{B},\text{C},?\) 的字符串 \(s,t\),求有多少种把 \(?\) 替换成 \(\text{A},\text{B},\text{C}\) 的方案,使得 \((s,t)\) 是势不两立的,对 \(998244353\) 取模。

要求线性求解。

\(\mathbb{S}\rm olution\)

这种题对于我这种弱鸡真的太不可做了,模拟得不了分,找规律又不会,证结论更是不会……

首先其实可以发现,\((s_i,s_{i+1},t_i,t_{i+1})\) 一定有一对匹配,所以最长公共子序列的长度至少为 \(n\)

然后给出结论 —— 势不两立的串一定与下面两种之一本质相同:

  1. \(s=\text{ABAB...ABA}\)\(t\) 在奇数位置均为 \(\rm C\),偶数位置可以 \(\text{A},\text{B}\) 任选。所以遇见这类题尝试构造一种方案也不失为一种思路?考虑偶数位置有恰好 \(n\) 个,所以不妨假设将 \(t\) 的偶数位全都匹配,就得到了这组非常优美的构造(我在说什么;
  2. \(s,t\) 在偶数位置均为 \(\rm C\),并且存在一个 \(k\),使得 \(s\) 的前 \(k\) 个奇数位置为 \(\rm A\),后 \(n+1-k\) 个奇数位置为 \(\rm B\)\(t\) 则相反。这种情况可以归纳证明是势不两立的:首先可以发现最长公共子序列只有可能包含 \(\{\text{C}\}\)\(\{\text{A,C}\}\)\(\{\text{B,C}\}\)。容易发现后面两种其实是等价的,而第一种的最长公共子序列显然为 \(n\),所以我们只考虑第二种情况。另外还可以发现,\(k>n+1-k\) 的情况严格包含于 \(k\le n+1-k\) 之中,所以我们只用考虑后者。当 \(k<n+1-k\) 时(没有将符号打错),\(s\) 中的 \(\text{ACAC...AC}\) 一定能被 \(t\) 中的 \(\text{CACA...CA}\) 全部匹配完,总长为 \(2k\),在这之后,如果 \(t\) 中的 \(\text{CACA...CA}\) 还有富余,可以再匹配一段 \(\rm C\),长度为 \((n+1-k)-k-1=n-2k\),所以最长公共子序列长度为 \(n\)。不过我们还没有考虑开头匹配了 \(\rm C\) 的情况,事实上可以发现,每当开头匹配一个 \(\rm C\),就意味着 至少 少匹配一个 \(\rm A\)(画画图可以知道这是一个类似势能的玩意),所以不可能更优。当 \(k=n+1-k\) 时的证明思路也大体相似,只是结论可能有些许不同,这里不再赘述。

令人惊讶的是,所有势不两立的 \((s,t)\) 都包括于上述两类。所以直接按这两种类型计数即可。

证明?一是因为证明很长很烦,二是因为它好像有点问题,所以就咕了。

$\mathbb{C}\rm ode $

# include <cstdio>
# include <cctype>
# define print(x,y) write(x), putchar(y)

template <class T>
inline T read(const T sample) {
    T x=0; char s; bool f=0;
    while(!isdigit(s=getchar())) f|=(s=='-');
    for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
    return f? -x: x;
}
template <class T>
inline void write(T x) {
    static int writ[50], w_tp=0;
    if(x<0) putchar('-'), x=-x;
    do writ[++w_tp] = x-x/10*10, x/=10; while(x);
    while(putchar(writ[w_tp--]^48), w_tp);
}

const int maxn = 1e6+5;
const int mod = 998244353;

char s[maxn<<1],t[maxn<<1];
int n,m,pw[maxn];
long long ans;

inline bool eq(const char& a,const char& b) {
	return a=='?' || a==b;
}

inline void check1(char* s,char* t,const char& a,const char& b,const char& c) {
	for(int i=1;i<=m;i+=2) if(!eq(s[i],a) || !eq(t[i],c)) return;
	int cnt=0;
	for(int i=2;i<=m;i+=2) {
		if(!eq(s[i],b) || t[i]==c) return;
		cnt += t[i]=='?';
	}
	ans = (ans+pw[cnt])%mod;
}

inline void check2(const char& a,const char& b,const char& c) {
	for(int i=2;i<=m;i+=2) if(!eq(s[i],c) || !eq(t[i],c)) return;
	int cnt=0;
	for(int i=1;i<=m;i+=2) cnt += eq(s[i],a)+eq(t[i],b);
	ans -= (cnt==2*(n+1));
	for(int i=1;i<m;i+=2)
		cnt += eq(s[i],b)-eq(s[i],a) + eq(t[i],a)-eq(t[i],b),
		ans += (cnt==2*(n+1));
}

void calc(const char& a,const char& b,const char& c) {
	check1(s,t,a,b,c); check1(t,s,a,b,c);
	check2(a,b,c);
}

void goto_solve_it() {
	scanf("%d %s %s",&n,s+1,t+1);
	m=n*2+1; ans=0;
	for(int i='A'; i<='C'; ++i)
		for(int j='A'; j<='C'; ++j)
			for(int k='A'; k<='C'; ++k)
				if(i!=j && j!=k && i!=k) calc(i,j,k);
	print((ans%mod+mod)%mod,'\n');
}

int main() {
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	for(int i=(pw[0]=1);i<=maxn-5;++i)
		pw[i] = 2ll*pw[i-1]%mod;
	for(int T=read(9); T; --T)
		goto_solve_it();
	return 0;
}

\(\cal T_2\) 铃原露露

\(\mathbb{D}\rm escription\)

给定一棵根为 \(1\) 的有根树,\(\{a\}\) 是一个排列。共 \(q\) 次询问,每次询问给出 \((l,r)\),询问有多少个二元组 \(L,R\),满足 \(l\le L\le R\le r\),且对任意 \(L\le a_x\le a_y\le R\),有 \(x,y\) 在树上的最近公共祖先 \(z\) 满足 \(L\le a_z\le R\)

\(1\le n,m\le 2\cdot 10^5\).

\(\mathbb{S}\rm olution\)

历史最值线段树,但是现在只会 \(50\) 分,等复习到了再回来做(

$\mathbb{C}\rm ode $


\(\cal T_3\) 赫露艾斯塔

\(\mathbb{D}\rm escription\)

给定 \(n\) 个互不相同的点 \((x_i,y_i)\),共 \(m\) 次询问,每次询问给出 \(A,B,C\),问满足 \(x_i<x_j,y_i<y_j,Ax_i+By_i+C>0,Ax_j+By_j+C>0\) 的二元组 \((x_i,y_i)\) 的个数。

\(A^2+B^2>0,|A|,|B|,|C|\le 10^8,1\le n,m\le 2\cdot 10^5,|x_i|,|y_i|\le 10^4\),其中 \(x_i,y_i\) 保证随机。

\(\mathbb{S}\rm olution\)

\(\bold{Warning}\):这题不会,现在只会可怜的 \(A=0\) 部分分。


如果 \(A=0\),显然可以将 \(Ax_i+By_i+C>0\) 的条件转化成 \(y_i>-\frac{C}{B}\) 或者 \(y_i<-\frac{C}{B}\),你会发现符合条件的点正好是经 \(y\) 坐标排序后的一段前/后缀,所以可以预处理一段前/后缀的答案,询问时二分即可。

但是我不是这样想的,我当时一看,诶嘛,这不是三维偏序吗?可怜的我根本没有意识到所谓的第三维根本没有施加在点对上,所以就写了个非常伞兵的 \(\rm cdq\),还是两只 \(\log\) 的……

$\mathbb{C}\rm ode $

# include <cstdio>
# include <cctype>
# define print(x,y) write(x), putchar(y)

template <class T>
inline T read(const T sample) {
    T x=0; char s; bool f=0;
    while(!isdigit(s=getchar())) f|=(s=='-');
    for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
    return f? -x: x;
}
template <class T>
inline void write(T x) {
    static int writ[50], w_tp=0;
    if(x<0) putchar('-'), x=-x;
    do writ[++w_tp] = x-x/10*10, x/=10; while(x);
    while(putchar(writ[w_tp--]^48), w_tp);
}

# include <iostream>
# include <algorithm>
using namespace std;
typedef long long ll;

const int maxn = 2e5+5;

ll ans;
int n,m,val[maxn],rag;
struct node {
    int x,y;
    bool operator < (const node& t) { return y<t.y; }
} s[maxn];

namespace FwTree {

int c[maxn];

inline int lowbit(int x) { return x&-x; }
inline int ask(int x,int ret=0) {
    for(; x; x-=lowbit(x)) ret += c[x];
    return ret;
}
inline void add(int x,int k) {
    for(; x<=rag; x+=lowbit(x)) c[x] += k;
}

}

namespace Subtask_1 {

node Node[1005];

void work() {
    int len;
    while(m --) {
        ll a=read(9), b=read(9), C=read(9); 
        ans=0; int fro=1; len=0;
        for(int i=1;i<=n;++i) {
            FwTree::c[i]=0;
            if(a*s[i].x+b*s[i].y+C>0) 
                Node[++len] = (node){
                    int(lower_bound(val+1,val+rag+1,s[i].x)-val),
                    s[i].y
                };
        }
        sort(Node+1,Node+len+1);
        for(int i=1; i<=len; ++i)
            if(Node[i].y!=Node[fro].y) {
                for(int j=fro; j<i; ++j)
                    FwTree::add(Node[j].x,1);
                fro = i; ans += FwTree::ask(Node[i].x-1);
            } else ans += FwTree::ask(Node[i].x-1);
        print(ans,'\n');
    }
}

}

namespace Subtask_2 {

bool option;
ll res[maxn],ret[maxn];
struct Node { int opt,p; double r; };
Node seq[2][maxn<<1];

void dicon(Node* f,int l,int r) { 
    if(l>=r) return;
    int mid = l+r>>1; dicon(f,l,mid);
    sort(f+mid+1,f+r+1,[](const Node& x,const Node& y) {
        return x.r<y.r;
    });
    sort(f+l,f+mid+1,[](const Node& x,const Node& y) {
        return x.r<y.r;
    });
    ll all=0; int pos=l;
    for(int i=l;i<=mid;++i) all += ret[f[i].opt];
    for(int i=mid+1;i<=r;++i) 
        if(f[i].opt) {
            while(pos<=mid && f[pos].r<f[i].r) {
                if(f[pos].opt) FwTree::add(f[pos].p,1);
                ++ pos;
            }
            ret[f[i].opt] += FwTree::ask(f[i].p-1);
        } else res[f[i].p] += all;
    for(int i=l;i<pos;++i) if(f[i].opt) FwTree::add(f[i].p,-1);
    pos = mid;
    for(int i=r;i>mid;--i)
        if(f[i].opt) {
            while(pos>=l && f[pos].r>f[i].r) {
                if(f[pos].opt) FwTree::add(rag-f[pos].p+1,1);
                -- pos;
            }
            ret[f[i].opt] += FwTree::ask(rag-f[i].p);
        }
    for(int i=mid;i>pos;--i) if(f[i].opt) FwTree::add(rag-f[i].p+1,-1);
    if(option)
        sort(f+mid+1,f+r+1,[](const Node& x,const Node& y) {
            return (x.r!=y.r)? x.r<y.r: (x.opt^y.opt)? x.opt<y.opt: x.p<y.p;
        }); 
    else 
        sort(f+mid+1,f+r+1,[](const Node& x,const Node& y) {
            return (x.r!=y.r)? x.r>y.r: (x.opt^y.opt)? x.opt<y.opt: x.p<y.p;
        }); 
    dicon(f,mid+1,r);
}

void work() {
    int m1=0, m2=0;
    for(int i=1;i<=n;++i) {
        s[i].x = lower_bound(val+1,val+rag+1,s[i].x)-val;
        seq[0][++m1] = (Node){i,s[i].x,0.0+s[i].y};
        seq[1][++m2] = (Node){i,s[i].x,0.0+s[i].y};
    }
    for(int i=1;i<=m;++i) {
        int a=read(9), b=read(9), c=read(9);
        if(b>0) seq[0][++m1] = (Node){0,i,-1.0*c/b};
        else seq[1][++m2] = (Node){0,i,-1.0*c/b};
    }
    sort(seq[0]+1,seq[0]+m1+1,[](const Node& x,const Node& y) {
        return (x.r!=y.r)? x.r>y.r: (x.opt^y.opt)? x.opt<y.opt: x.p<y.p;
    });
    sort(seq[1]+1,seq[1]+m2+1,[](const Node& x,const Node& y) {
        return (x.r!=y.r)? x.r<y.r: (x.opt^y.opt)? x.opt<y.opt: x.p<y.p;
    });
    if(m1>n) dicon(seq[0],1,m1); option=true;
    for(int i=1;i<=n;++i) ret[i]=0; 
    if(m2>n) dicon(seq[1],1,m2);
    for(int i=1;i<=m;++i) print(res[i],'\n');
}

}

int main() {
	freopen("c.in","r",stdin);
	freopen("c.out","w",stdout);
	n=read(9), m=read(9);
    for(int i=1;i<=n;++i)
        val[i]=s[i].x=read(9), s[i].y=read(9);
    sort(val+1,val+n+1); rag = unique(val+1,val+n+1)-val-1;
    if(n<=1000 && m<=1000) return Subtask_1::work(), (0-0);
    Subtask_2::work();
	return 0;
}
posted on 2022-05-08 10:37  Oxide  阅读(33)  评论(0编辑  收藏  举报