IOI2020集训队作业-22 (CF587F, CF685C, ARC096E)

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 skT,差分将 [ l , r ] [l,r] [l,r]转化成前缀,然后进行扫描线。扫到 r r r l − 1 l-1 l1的时候,枚举 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+zxiyiziAnsx+y+zixiyizAnsx+yi+zxiyziAnsx+yi+zixiyzAnsxi+y+zxyiziAnsxi+y+zixyizAnsxi+yi+zxyziAnsxi+yi+zixyzAns

移项、整理后,可以得到这样的形式:

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 \\ l0x+y+zr0l1x+yzr1l2xy+zr2l3x+y+zr3

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+yz,b=x+zy,c=y+zx,则 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] [2a]=[2b]=[2c],就可以由 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' l0a+b+cr0l1ar1l2br2l3cr3

由后面三个式子可以知道 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+l3a+b+cr1+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(2ni)j22ni,其中 ( 2 n − i ) j (2^{n-i})^j (2ni)j表示这 j j j个集合的每一个都可以选择加或者不加剩下的 n − i n-i ni种调料, 2 2 n − i 2^{2^{n-i}} 22ni表示仅由剩下的 n − i n-i ni种调料组成的可能的 2 n − i 2^{n-i} 2ni种调料集合可以出现或者不出现。

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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值