\(\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\)。
然后给出结论 —— 势不两立的串一定与下面两种之一本质相同:
- \(s=\text{ABAB...ABA}\),\(t\) 在奇数位置均为 \(\rm C\),偶数位置可以 \(\text{A},\text{B}\) 任选。所以遇见这类题尝试构造一种方案也不失为一种思路?考虑偶数位置有恰好 \(n\) 个,所以不妨假设将 \(t\) 的偶数位全都匹配,就得到了这组非常优美的构造(我在说什么;
- \(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;
}