A - CF587F Duff is Mad
Sol
首先对所有的串建出广义后缀自动机。
设置一个在 1 0 5 \sqrt {10^5} 105左右的阈值 T T T,令 U = 1 0 5 T U={10^5\over T} U=T105。
如果 ∣ s k ∣ > T |s_k|>T ∣sk∣>T,这样的 s k s_k sk一定不超过 U U U个。将询问离线下来之后,对于每一个 ∣ s k ∣ > T |s_k|>T ∣sk∣>T的 s k s_k sk,在 f a i l fail fail树上 d f s dfs dfs求出每一个串在它里面的出现次数,然后 O ( 1 ) O(1) O(1)回答询问。
如果 ∣ s k ∣ ≤ T |s_k|\le T ∣sk∣≤T,差分将 [ l , r ] [l,r] [l,r]转化成前缀,然后进行扫描线。扫到 r r r或 l − 1 l-1 l−1的时候,枚举 s k s_k sk的每一个前缀,查这个前缀在后缀树上的祖先有多少个被扫到过。这样会进行 O ( n ) O(n) O(n)次修改和 O ( n n ) O(n\sqrt n) O(nn)次查询,用 O ( n ) O(\sqrt n) O(n)修改和 O ( 1 ) O(1) O(1)查询的分块进行维护,总复杂度 O ( n n ) O(n\sqrt n) O(nn)。
Code
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#define PB push_back
#define MP make_pair
#define FIR first
#define SEC second
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
x=0; char c=getchar(); int f=1;
while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
vector<int> vir[200010];
int tg[200010];
ll buc[100010];
namespace Tree {
const int N=2e5+10;
vector<int> son[N];
void ad(int f,int x) { son[f].PB(x); }
int dfn[N],rdfn[N],id;
void dfs(int u) {
dfn[u]=++id;
for(int i=0;i<son[u].size();++i)
dfs(son[u][i]);
rdfn[u]=id;
}
void geti(int u,int &l,int &r) { l=dfn[u],r=rdfn[u]; }
void sol(int u) {
for(int i=0;i<son[u].size();++i)
sol(son[u][i]),tg[u]+=tg[son[u][i]];
for(int i=0;i<vir[u].size();++i)
buc[vir[u][i]]+=tg[u];
}
}
using Tree::dfn;
using Tree::geti;
namespace SAM {
const int N=2e5+10;
int ch[N][26],len[N],fail[N],rt=1,last,ncnt=1;
int cpy(int c) { int u=++ncnt; memcpy(ch[u],ch[c],sizeof(ch[u])); return u; }
int work(int p,int c) {
int q=ch[p][c],cl=cpy(q); len[cl]=len[p]+1;
fail[cl]=fail[q],fail[q]=cl;
for(;ch[p][c]==q;p=fail[p]) ch[p][c]=cl;
return cl;
}
int insert(int c) {
int p=last;
if(ch[p][c]) return len[ch[p][c]]==len[p]+1?ch[p][c]:work(p,c);
int cur=++ncnt; len[cur]=len[last]+1;
for(;p&&!ch[p][c];p=fail[p]) ch[p][c]=cur;
if(!p) fail[cur]=rt;
else fail[cur]=(len[ch[p][c]]==len[p]+1?ch[p][c]:work(p,c));
return cur;
}
void ins(char *s,int *p,int len,int id) {
last=rt;
for(int i=1;i<=len;++i) p[i]=last=insert(s[i]-'a');
vir[p[len]].PB(id);
}
void build() {
for(int i=1;i<=ncnt;++i) if(fail[i]) Tree::ad(fail[i],i);
}
}
const int BLOCK=400;
namespace DS {
int a[510][410],len[510];
int bel[200010],rnk[200010];
ll s1[510][410],s2[510];
int m;
void init(int n) {
for(int cur=1;cur<=n;cur+=BLOCK) {
len[++m]=min(BLOCK,n-cur+1);
for(int j=0;j<len[m];++j)
bel[cur+j]=m,rnk[cur+j]=j+1;
}
}
void recal(int p) {
for(int i=1;i<=len[p];++i)
s1[p][i]=s1[p][i-1]+a[p][i];
for(int i=1;i<=m;++i)
s2[i]=s2[i-1]+s1[i][len[i]];
}
void upd(int x,int d) {
a[bel[x]][rnk[x]]+=d;
recal(bel[x]);
}
void upd(int l,int r,int d) {
upd(l,d),upd(r+1,-d);
}
ll query(int r) {
return s2[bel[r]-1]+s1[bel[r]][rnk[r]];
}
}
char str[100010];
int *p[100010],len[100010],n,q;
ll ans[100010];
struct Que {
int l,r,id;
Que(int l=0,int r=0,int id=0): l(l),r(r),id(id) {}
};
vector<Que> Q1[100010];
vector<Que> Q2[100010];
int main() {
rd(n),rd(q);
for(int i=1;i<=n;++i) {
scanf("%s",str+1);
len[i]=strlen(str+1);
p[i]=new int[len[i]+10];
SAM::ins(str,p[i],len[i],i);
}
SAM::build();
Tree::dfs(1);
for(int i=1,k,l,r;i<=q;++i) {
rd(l),rd(r),rd(k);
if(len[k]>BLOCK) Q1[k].PB(Que(l,r,i));
else {
Q2[l-1].PB(Que(k,-1,i)),
Q2[r].PB(Que(k,1,i));
}
}
DS::init(SAM::ncnt);
for(int i=1;i<=n;++i) {
int l,r; geti(p[i][len[i]],l,r);
DS::upd(l,r,1);
for(int j=0;j<Q2[i].size();++j) {
int k=Q2[i][j].l;
for(int x=1;x<=len[k];++x)
ans[Q2[i][j].id]+=Q2[i][j].r*DS::query(dfn[p[k][x]]);
}
}
for(int i=1;i<=n;++i) if(Q1[i].size()) {
memset(buc,0,sizeof(buc));
memset(tg,0,sizeof(tg));
for(int j=1;j<=len[i];++j) tg[p[i][j]]++;
Tree::sol(1);
for(int j=1;j<=n;++j) buc[j]+=buc[j-1];
for(int j=0;j<Q1[i].size();++j) ans[Q1[i][j].id]=buc[Q1[i][j].r]-buc[Q1[i][j].l-1];
}
for(int i=1;i<=q;++i) printf("%lld\n",ans[i]);
return 0;
}
B - CF685C Optimal Point
Sol
二分答案。设选择的点为 ( x , y , z ) (x,y,z) (x,y,z),则等价于限制了:
x + y + z − x i − y i − z i ≥ A n s x + y + z i − x i − y i − z ≥ A n s x + y i + z − x i − y − z i ≥ A n s x + y i + z i − x i − y − z ≥ A n s x i + y + z − x − y i − z i ≥ A n s x i + y + z i − x − y i − z ≥ A n s x i + y i + z − x − y − z i ≥ A n s x i + y i + z i − x − y − z ≥ A n s x + y + z - x_i - y_i - z_i \ge Ans \\ x + y + z_i - x_i - y_i - z \ge Ans \\ x + y_i + z - x_i - y - z_i \ge Ans \\ x + y_i + z_i - x_i - y - z \ge Ans \\ x_i + y + z - x - y_i - z_i \ge Ans \\ x_i + y + z_i - x - y_i - z \ge Ans \\ x_i + y_i + z - x - y - z_i \ge Ans \\ x_i + y_i + z_i - x - y - z \ge Ans \\ x+y+z−xi−yi−zi≥Ansx+y+zi−xi−yi−z≥Ansx+yi+z−xi−y−zi≥Ansx+yi+zi−xi−y−z≥Ansxi+y+z−x−yi−zi≥Ansxi+y+zi−x−yi−z≥Ansxi+yi+z−x−y−zi≥Ansxi+yi+zi−x−y−z≥Ans
移项、整理后,可以得到这样的形式:
l 0 ≤ x + y + z ≤ r 0 l 1 ≤ x + y − z ≤ r 1 l 2 ≤ x − y + z ≤ r 2 l 3 ≤ − x + y + z ≤ r 3 l_0 \le x + y + z\le r_0 \\ l_1 \le x + y - z\le r_1 \\ l_2 \le x - y + z\le r_2 \\ l_3 \le - x + y + z\le r_3 \\ l0≤x+y+z≤r0l1≤x+y−z≤r1l2≤x−y+z≤r2l3≤−x+y+z≤r3
令 a = x + y − z , b = x + z − y , c = y + z − x a=x+y-z,b=x+z-y,c=y+z-x a=x+y−z,b=x+z−y,c=y+z−x,则 a + b + c = x + y + z a+b+c = x+ y+z a+b+c=x+y+z,并且只要 [ 2 ∣ a ] = [ 2 ∣ b ] = [ 2 ∣ c ] [2\mid a] = [2\mid b] = [2\mid c] [2∣a]=[2∣b]=[2∣c],就可以由 a , b , c a,b,c a,b,c得到唯一确定的整数 ( x , y , z ) (x,y,z) (x,y,z)。
枚举 a ( m o d 2 ) a\pmod 2 a(mod2),然后移项、整理,可以得到对 a , b , c a,b,c a,b,c没有限制的下面的式子:
l 0 ′ ≤ a + b + c ≤ r 0 ′ l 1 ′ ≤ a ≤ r 1 ′ l 2 ′ ≤ b ≤ r 2 ′ l 3 ′ ≤ c ≤ r 3 ′ l_0' \le a+b+c\le r_0'\\ l_1' \le a\le r_1'\\ l_2' \le b\le r_2'\\ l_3' \le c\le r_3' l0′≤a+b+c≤r0′l1′≤a≤r1′l2′≤b≤r2′l3′≤c≤r3′
由后面三个式子可以知道 l 1 ′ + l 2 ′ + l 3 ′ ≤ a + b + c ≤ r 1 ′ + r 2 ′ + r 3 ′ l_1'+l_2'+l_3'\le a+b+c\le r_1'+r_2'+r_3' l1′+l2′+l3′≤a+b+c≤r1′+r2′+r3′,判断这个范围和 [ l 0 ′ , r 0 ′ ] [l_0',r_0'] [l0′,r0′]是否有交就可以知道是否有解。
Code
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
#define PB push_back
#define MP make_pair
#define FIR first
#define SEC second
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
x=0; char c=getchar(); int f=1;
while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
template <class T> inline void cmin(T &x,T y) { if(y<x) x=y; }
template <class T> inline void cmax(T &x,T y) { if(y>x) x=y; }
const ll inf=(1ll<<62);
const int N=1e5+10;
ll lb[4],rb[4],L[4],R[4];
ll xi[N],yi[N],zi[N];
int n;
bool check(ll mid,int fflg=0) {
for(int i=0;i<4;++i) lb[i]=-3e18,rb[i]=3e18;
// cout<<lb[0]<<' '<<-xi[1]-yi[1]-zi[1]<<endl;
// cout<<rb[0]<<' '<<xi[1]+yi[1]+zi[1]<<endl;
for(int i=1;i<=n;++i) {
ll x=xi[i],y=yi[i],z=zi[i];
cmin(rb[0],x+y+z+mid);
cmax(lb[0],x+y+z-mid);
cmin(rb[1],-x+y+z+mid);
cmax(lb[1],-x+y+z-mid);
cmin(rb[2],x-y+z+mid);
cmax(lb[2],x-y+z-mid);
cmin(rb[3],x+y-z+mid);
cmax(lb[3],x+y-z-mid);
}
for(int r=0;r<2;++r) {
L[0]=lb[0]-3*r,R[0]=rb[0]-3*r;
for(int i=1;i<4;++i) L[i]=lb[i]-r,R[i]=rb[i]-r;
for(int i=0;i<4;++i) L[i]=ceil(L[i]/(long double)2.0),R[i]=floor(R[i]/(long double)2.0);
int flg=1;
for(int i=0;i<4;++i) if(L[i]>R[i]) { flg=0; break; }
if(!flg) continue;
ll l1=L[1]+L[2]+L[3],r1=R[1]+R[2]+R[3];
if(R[0]>=l1&&r1>=L[0]) {
if(fflg) {
ll a=L[1],b=L[2],c=L[3];
if(a+b+c<L[0]) a+=min(R[1]-L[1],L[0]-a-b-c);
if(a+b+c<L[0]) b+=min(R[2]-L[2],L[0]-a-b-c);
if(a+b+c<L[0]) c+=min(R[3]-L[3],L[0]-a-b-c);
ll x=b+c+r,y=a+c+r,z=a+b+r;
cout<<x<<' '<<y<<' '<<z<<endl;
}
return 1;
}
}
return 0;
}
int main() {
int T; rd(T);
while(T--) {
rd(n);
for(int i=1;i<=n;++i) rd(xi[i]),rd(yi[i]),rd(zi[i]);
ll lb=0,rb=3e18;
while(lb<rb) {
ll mid=lb+rb>>1;
if(check(mid)) rb=mid;
else lb=mid+1;
}
check(lb,1);
}
return 0;
}
C - ARC096E Everything on It
Sol
考虑容斥。设 f i , j f_{i,j} fi,j表示有 i i i种调料,每种可以出现至多一次,组合成 j j j个非空集合的方案数。 f f f可以通过简单递推在 O ( n 2 ) O(n^2) O(n2)的时间内得到。则至少有 i i i种调料出现了少于 2 2 2次的方案数是: ( n i ) f i , j ⋅ ( 2 n − i ) j ⋅ 2 2 n − i {n\choose i}f_{i,j} \cdot (2^{n-i})^j \cdot 2^{2^{n-i}} (in)fi,j⋅(2n−i)j⋅22n−i,其中 ( 2 n − i ) j (2^{n-i})^j (2n−i)j表示这 j j j个集合的每一个都可以选择加或者不加剩下的 n − i n-i n−i种调料, 2 2 n − i 2^{2^{n-i}} 22n−i表示仅由剩下的 n − i n-i n−i种调料组成的可能的 2 n − i 2^{n-i} 2n−i种调料集合可以出现或者不出现。
Code
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#define PB push_back
#define MP make_pair
#define FIR first
#define SEC second
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
x=0; char c=getchar(); int f=1;
while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int N=3010;
int mod;
int Pow(int x,int y) {
int res=1;
while(y) {
if(y&1) res=res*(ll)x%mod;
x=x*(ll)x%mod,y>>=1;
}
return res;
}
int Pow(int x,int y,int mod) {
int res=1;
while(y) {
if(y&1) res=res*(ll)x%mod;
x=x*(ll)x%mod,y>>=1;
}
return res;
}
int C[N][N],n;
int pw[N*N];
int f[N][N];
int main() {
rd(n),rd(mod);
for(int i=0;i<=n;++i) for(int j=0;j<=i;++j) C[i][j]=j?(C[i-1][j-1]+C[i-1][j])%mod:1;
pw[0]=1; for(int i=1;i<=n*n;++i) pw[i]=pw[i-1]*2ll%mod;
f[0][0]=1;
for(int i=1;i<=n;++i)
for(int j=0;j<=i;++j)
f[i][j]=(f[i-1][j]*(ll)(j+1)+f[i-1][j-1])%mod;
int ans=0;
for(int i=0;i<=n;++i) {
int tot=0;
for(int j=0;j<=i;++j) tot=(tot+f[i][j]*(ll)pw[(n-i)*j]%mod)%mod;
tot=tot*(ll)Pow(2,Pow(2,n-i,mod-1))%mod;
ans=(ans+C[n][i]*(ll)tot*((i&1)?-1:1))%mod;
}
printf("%d",(ans+mod)%mod);
return 0;
}