【2018.11.26】玄机 / 画心 / 求索

以后就是 $NOI$ 模拟赛了!!!

题目

WZJ题解

 

T1

一开考就口胡了这道题……

首先遇到这种总字符数特别少的多模板串匹配,肯定要建一个 $AC$ 自动机。

然后想到了维护矩阵,同时记录 转移到的点 和 匹配过的点的集合。

但是这样的复杂度是 $O((50\times m)^3\times log(n))$,会被卡。

所以不能维护 匹配过的点的集合,把它抽到外面去,这样能省很多矩乘复杂度。

不知道你还记不记得这么一道题:GT考试

那一题是不让你匹配出不吉利数字串。

然后回来看看这题,发现两者求的东西其实是相反的。

所以这题可以枚举不出现哪些串,在预处理转移矩阵时,不让根节点能够转移到一个点,使得根节点到这个点的路径组成的串的后缀为一个不能出现的串。

由于转移矩阵经过一次转移,最多多匹配一位,也就是匹配到它的儿子,所以对于一个后缀为一个不能出现的串的串在 $AC$ 自动机上的底部节点(可能不存在,那就不用管),当匹配到这个点的父亲时,判断一下,不让它匹配到这个点就行了。这样就没法转移到这个点及其下面的所有点了。

最后套一个容斥原理就行了:不出现任意一个串的情况数 = 不出现 $1$ 个串的情况数 - 不出现 $2$ 个串的情况数 + 不出现 $3$ 个串的情况数 - 不出现 $4$ 个串的情况数。

答案就是 总情况数 - 不出现任意一个串的情况数。

时间复杂度 $O(2^m\times 50^3\times log(n))$。

 

另外一定要搞清矩阵的横乘和纵乘是不一样的!!横乘是 $a(i,k)\times b(k,j)$,纵乘是 $b(i,k)\times 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('\n');
 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\n",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('\n');
113         }*/
114         ans+=qpow(n)*x;
115       //  printf("Hey buddy:%d %d %lld %lld\n",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\n",((qpow(10,n)-ans)%mod+mod)%mod);
136     return 0;
137 }    
View Code

 

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\n",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\n",l,r);
54     for(i=l;i<=r;++i) printf("%d %d\n",id[i],s[id[i]]);
55     putchar('\n');*/
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\n",f[i]);
67     printf("%lld\n",f[n]+(ll)s[n]*b);
68     return 0;
69 }
View Code

 

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\times (sum_a+sum_b)$。

我们发现两个条件其实就是二维偏序,可以递推+数据结构($cdq$ 患者除外)统计答案,对于 $a≠b$,正反各跑一遍即可。

所以依次遍历重心的儿子(其实编号顺序就是随意的,这个顺序也随意),当遍历到第 $i$ 个时,把 $sum_i$ 插入树状数组的第 $Min_a$ 位,然后把 $ans$ 累加上 $Min_b\times querySum(Min_b,inf)$(其中 $querySum(l,r)$ 表示查询树状数组 $[l,r)$ 区间的和,前缀和相减即可求出)。

 

好,$100$ 分又是线段树分治,不会

 

posted @ 2018-11-26 18:40  大本营  阅读(199)  评论(0编辑  收藏  举报