以后就是 $NOI$ 模拟赛了!!!
T1
一开考就口胡了这道题……
首先遇到这种总字符数特别少的多模板串匹配,肯定要建一个 $AC$ 自动机。
然后想到了维护矩阵,同时记录 转移到的点 和 匹配过的点的集合。
但是这样的复杂度是 $O((50 imes m)^3 imes log(n))$,会被卡。
所以不能维护 匹配过的点的集合,把它抽到外面去,这样能省很多矩乘复杂度。
不知道你还记不记得这么一道题:GT考试
那一题是不让你匹配出不吉利数字串。
然后回来看看这题,发现两者求的东西其实是相反的。
所以这题可以枚举不出现哪些串,在预处理转移矩阵时,不让根节点能够转移到一个点,使得根节点到这个点的路径组成的串的后缀为一个不能出现的串。
由于转移矩阵经过一次转移,最多多匹配一位,也就是匹配到它的儿子,所以对于一个后缀为一个不能出现的串的串在 $AC$ 自动机上的底部节点(可能不存在,那就不用管),当匹配到这个点的父亲时,判断一下,不让它匹配到这个点就行了。这样就没法转移到这个点及其下面的所有点了。
最后套一个容斥原理就行了:不出现任意一个串的情况数 = 不出现 $1$ 个串的情况数 - 不出现 $2$ 个串的情况数 + 不出现 $3$ 个串的情况数 - 不出现 $4$ 个串的情况数。
答案就是 总情况数 - 不出现任意一个串的情况数。
时间复杂度 $O(2^m imes 50^3 imes log(n))$。
另外一定要搞清矩阵的横乘和纵乘是不一样的!!横乘是 $a(i,k) imes b(k,j)$,纵乘是 $b(i,k) imes a(k,j)$!!现场用垃圾纵乘然后写反,成功表演爆 $0$……
1 #include<bits/stdc++.h> 2 #define ll long long 3 #define mod 998244353 4 using namespace std; 5 inline int read(){ 6 int x=0; bool f=1; char c=getchar(); 7 for(;!isdigit(c);c=getchar()) if(c=='-') f=0; 8 for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^'0'); 9 if(f) return x; 10 return 0-x; 11 } 12 int m,mm,n,tag[55]; 13 ll f[55][55],ans; 14 char c[5][55]; 15 16 int que[55],nxt[55]; 17 namespace AC{ 18 int tr[55][26],Tr[55][26],mark[55],cnt; 19 void ins(char ch[55],int id){ 20 int len=strlen(ch),i,u=0,v; 21 for(i=0;i^len;++i){ 22 v=ch[i]^'0'; 23 if(!tr[u][v]) tr[u][v]=Tr[u][v]=++cnt; 24 u=tr[u][v]; 25 } 26 mark[u]|=(1<<id); 27 } 28 void getFail(){ 29 int i,u,hd=1,tl=0; 30 for(int i=0;i^10;++i) if(tr[0][i]) que[++tl]=tr[0][i]; 31 while(hd<=tl){ 32 u=que[hd++], mark[u]|=mark[nxt[u]]; 33 for(i=0;i^10;++i){ 34 if(!tr[u][i]) {tr[u][i]=tr[nxt[u]][i];} 35 else{ 36 que[++tl]=tr[u][i]; 37 nxt[tr[u][i]]=tr[nxt[u]][i]; 38 } 39 } 40 } 41 } 42 }; 43 using namespace AC; 44 void mul(ll a[55][55], ll b[55][55]){ 45 int i,j,k; ll tmp[55][55]; 46 /* cout<<"++"<<endl; 47 for(i=0;i<=cnt;++i){ 48 for(j=0;j<=cnt;++j) 49 printf("%d ",b[i][j]); 50 putchar(' '); 51 }*/ 52 //cout<<"++"<<endl; 53 for(i=0;i<=cnt;++i) 54 for(j=0;j<=cnt;++j){ 55 tmp[i][j]=0; 56 for(k=0;k<=cnt;++k) 57 (tmp[i][j]+=a[k][j]*b[i][k])%=mod; 58 } 59 for(i=0;i<=cnt;++i) 60 for(j=0;j<=cnt;++j) 61 a[i][j]=tmp[i][j]; 62 } 63 ll a[55][55],b[55][55]; 64 ll qpow(ll x,int y){ 65 ll ret=1; 66 while(y>0){ 67 if(y&1) ret=(ret*x)%mod; 68 x=(x*x)%mod; 69 y>>=1; 70 } 71 return ret; 72 } 73 ll qpow(int x){ 74 int i,j; 75 memset(a,0,sizeof(a)); 76 a[0][0]=1; 77 while(x>0){ 78 if(x&1) mul(a,b); 79 mul(b,b); 80 x>>=1; 81 } 82 ll res=0; 83 for(i=0;i<=cnt;++i) res=(res+a[i][0])%mod; 84 return res; 85 } 86 void dfs(int u,int x,int mx){ 87 if(u){ 88 memset(b,0,sizeof b); 89 int i,j,t; 90 que[1]=0; 91 /* 92 for(int hd=1,tl=1;hd<=tl;++hd){ 93 i=que[hd]; 94 for(j=0;j^m;++j) 95 if(u&(1<<j) && tag[j]==i){ed[u]=1; break;} 96 for(j=0;j^10;++j) 97 if(Tr[i][j]) que[++tl]=Tr[i][j], ed[Tr[i][j]]=ed[i]; 98 }*/ 99 // for(int j=0;j<=cnt;++j)if(mark[j]&u)ed[j]=1; 100 //cout<<"-------"<<endl; 101 for(i=0;i<=cnt;++i){ 102 for(j=0;j^10;++j){ 103 t=tr[i][j]; 104 //printf("back:%d %d %d ",j,t,i); 105 if(!(mark[t]&u)) b[t][i]=(b[t][i]+1)%mod; 106 } 107 } 108 /*cout<<"-------"<<endl; 109 for(i=0;i<=cnt;++i){ 110 for(j=0;j<=cnt;++j) 111 printf("%d ",b[i][j]); 112 putchar(' '); 113 }*/ 114 ans+=qpow(n)*x; 115 // printf("Hey buddy:%d %d %lld %lld ",u,x,haha,ans); 116 ans=(ans%mod+mod)%mod; 117 } 118 if(!(u&1) && (u|1)<mm && 1>mx) dfs(u|1,-x,max(mx,1)); 119 if(!(u&2) && (u|2)<mm && 2>mx) dfs(u|2,-x,max(mx,2)); 120 if(!(u&4) && (u|4)<mm && 4>mx) dfs(u|4,-x,max(mx,4)); 121 if(!(u&8) && (u|8)<mm && 8>mx) dfs(u|8,-x,max(mx,8)); 122 } 123 int main(){ 124 //freopen("password.in","r",stdin); 125 //freopen("password.out","w",stdout); 126 m=read(),n=read(); 127 mm=1<<m; 128 int i,j; 129 for(i=0;i^m;++i){ 130 scanf("%s",c[i]); 131 ins(c[i],i); 132 } 133 getFail(); 134 dfs(0,-1,0); 135 printf("%lld ",((qpow(10,n)-ans)%mod+mod)%mod); 136 return 0; 137 }
T2
实(W)践(Z)证(J)明(shuo)线段树分治是基础操作
我不会基础操作,我该退役了 sro
容易发现 $40$ 分 $noip$ 难度的 $dp$ 是白送的。
$60$ 分是个简单递推维护凸包的斜率优化(因为前缀和 $S_i$ 是单调递增的,可以把队头小于当前斜率的线段 依次删掉),不过我好像很少写这个……
$80$ 分
1 #include<bits/stdc++.h> 2 #define N 152505 3 #define ll long long 4 #define ld long double 5 const ll inf=(1ll<<63)-1; 6 using namespace std; 7 inline int read(){ 8 int x=0; bool f=1; char c=getchar(); 9 for(;!isdigit(c);c=getchar()) if(c=='-') f=0; 10 for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^'0'); 11 if(f) return x; 12 return 0-x; 13 } 14 int n,a,b,c,l,r,s[N]; 15 int hd,tl,q[N]; 16 ll f[N]; 17 inline ll gy(int j){return f[j]+(ll)a*s[j]*s[j];} 18 inline ll getAns(int i,int j){return gy(j)-2ll*a*s[i]*s[j]+(ll)a*s[i]*s[i]+c;} 19 inline void push(int i){ 20 while(hd<tl && ((ld)gy(i)-(ld)gy(q[tl])) * ((ld)s[q[tl]]-(ld)s[q[tl-1]]) > ((ld)gy(q[tl])-(ld)gy(q[tl-1])) * ((ld)s[i]-(ld)s[q[tl]])) --tl; 21 q[++tl]=i; 22 } 23 inline bool jud(int i,int j){return (ld)gy(q[j+1])-(ld)gy(q[j]) <= (ld)2*(ld)a*(ld)s[i]*((ld)s[q[j+1]]-(ld)s[q[j]]);} 24 ll getMx(int i){ 25 ll ans=getAns(i,q[tl]); 26 int l=hd, r=tl-1, mid; 27 while(l<=r){ 28 mid=(l+r)>>1; 29 ans=max(ans,max(getAns(i,q[mid]),getAns(i,q[mid+1]))); 30 if(jud(i,mid)) r=mid-1; 31 else l=mid+1; 32 } 33 //printf("upd:%d %lld ",i,ans); 34 return ans; 35 } 36 int id[N],tmp[N]; 37 void cdq(int l,int r){ 38 if(l==r) return; 39 int mid=(l+r)>>1, i; 40 cdq(l,mid); 41 hd=1, tl=0, q[++tl]=id[l]; 42 for(i=l+1;i<=mid;++i) push(id[i]); 43 for(;i<=r;++i) f[i]=max(f[i],getMx(i)); 44 cdq(mid+1,r); 45 int now=0, j; 46 i=l; 47 for(j=mid+1;j<=r;++j){ 48 while(i<=mid && s[id[i]]<s[id[j]]) tmp[++now]=id[i++]; 49 tmp[++now]=id[j]; 50 } 51 while(i<=mid) tmp[++now]=id[i++]; 52 for(i=l;i<=r;++i) id[i]=tmp[i-l+1];/* 53 printf("cdq:%d %d ",l,r); 54 for(i=l;i<=r;++i) printf("%d %d ",id[i],s[id[i]]); 55 putchar(' ');*/ 56 } 57 int main(){ 58 //freopen("paint7.in","r",stdin); 59 //freopen("paint.out","w",stdout); 60 n=read(),a=read(),b=read(),c=read(),l=read(),r=read(); 61 int i; 62 for(i=1;i<=n;++i) s[i]=s[i-1]+read(), id[i]=i; 63 f[0]=0; 64 for(i=1;i<=n;++i) f[i]=-inf; 65 cdq(0,n); 66 //for(i=1;i<=n;++i) printf("%lld ",f[i]); 67 printf("%lld ",f[n]+(ll)s[n]*b); 68 return 0; 69 }
T3
$5-6$ 组数据 就是把边权为 $0$ 的边全部删掉,然后在剩下的每个连通块中用两次 $dfs$ 找最远点求出直径,最后取所有直径的最大值。
简单地说 $60$ 分以下是 $noip$ 难度。
$80$ 分段开始就要套用各种奇怪的树上算法了。
树上算法有几个?……数数好像没几个,而且这种题用点分治来暴力优化好像就可以了……
设重心的儿子依次编号为 $1$ 到 $x$,每次在一部分树中找到重心(分治点)后,把它的所有出边都暴力跑一遍,动态记录所有从重心出发、经过编号为 $i$ 的儿子的所有路径的二维信息(路径边权的最小值 $Min_i$,路径边权之和 $sum_i$)。
要合并两条路径为答案路径 $(x,y)$,设它们经过的儿子分别为 $a,b$,它们满足下列条件时
$$a≠b$$
$$Min_a>Minb$$
它们合并的答案为 $Min_b imes (sum_a+sum_b)$。
我们发现两个条件其实就是二维偏序,可以递推+数据结构($cdq$ 患者除外)统计答案,对于 $a≠b$,正反各跑一遍即可。
所以依次遍历重心的儿子(其实编号顺序就是随意的,这个顺序也随意),当遍历到第 $i$ 个时,把 $sum_i$ 插入树状数组的第 $Min_a$ 位,然后把 $ans$ 累加上 $Min_b imes querySum(Min_b,inf)$(其中 $querySum(l,r)$ 表示查询树状数组 $[l,r)$ 区间的和,前缀和相减即可求出)。
好,$100$ 分又是线段树分治,不会。