[考试反思]1022csp-s模拟测试82:奇异

Posted hzoi-deepinc

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[考试反思]1022csp-s模拟测试82:奇异相关的知识,希望对你有一定的参考价值。

技术图片

技术图片

这场考试的确颇为奇异。

两道结论题。。。(其中T1被称为送分题。。。)

决策不是很对。T1是一个只有一个参数的找规律,明显可以打表。

但是因为搜索不好打所以就没有打。。。

搜索再难打一个小时应该也可以模拟完了,不说推式子了,找规律肯定是能找到的啊。

然而我T1动都没动。。。

T3跪在了高考组合数学上。。。。

只有一个参数的题大多是结论题,可以尝试打表找规律。

不要乱弃题!

 

T1:

结论题。大规模推式子,因为只有一个参数,所以打表其实也很好做。

答案是$frac{n^2-1}{9}$

推式子先留一个坑。

upd:

设f[n]表示在长度为n的子序列里的每一对逆序对所产生的贡献(非逆序对在删除某些元素之后仍然是非逆序对,永远没贡献)

那么$f[n]=1+frac{sumlimits_{i=0}^{i=n}C_{n-2}^{n-i} imes f[i]}{2^n}$

具体含义是1是指这个逆序对本身的贡献,剩下的部分就是在其余n-2个元素里随便选而这个逆序对必选从而这个逆序对得到了保留继续产生f[i]的贡献。

那么最终的答案就是$frac{sumlimits_{i=1}^{n}C_n^2 imes C_n^2 imes A_i^{i-2}  imes f[i]}{n}$

具体含义是,先从所有元素中选出一个逆序对,然后在所有的位置里选出2个位置把它们放下去,然后把其它元素自由排列,这样产生了f[i]的贡献

当i>=2时,有$f[i]=frac{4}{3}$

归纳证明一下:当i=2时成立,当i>2时把式子回代依旧可以得到$f[i]=frac{4}{3}$

再化简一下ans的式子,把组合数和排列数全都拆开,得到$ans=frac{sumlimits_{i=1}^{n} i^2 - i}{3n}$

然后和式里面的可以套公式(和T3一样)得到$ans=frac{frac{n(n+1)(2n+1)}{6}-frac{(n+1)n}{2}}{3n}$

$=frac{frac{2n^2+3n+1}{6}-frac{n+1}{2}}{3}$

$=frac{n^2-1}{9}$

得证

技术图片
1 #include<cstdio>
2 #define mod 998244353
3 #define inv 443664157
4 main(){
5     int t;scanf("%d",&t);
6     long long n;
7     while(t--)scanf("%lld",&n),n%=mod,printf("%lld
",(n-1)*(n+1)%mod*inv%mod);
8 }
View Code

 

T2:

技术图片
 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<vector>
 5 using namespace std;
 6 long long ans;char s[1000005],S[1000005];int sL,pre_match[1000005],suc_match[1000005];;
 7 struct AC_automation{
 8     int cnt,rt,trie[1000005][27],fail[1000005],q[1000005];
 9     vector<int>v[1000005],w[1000005];
10     void build(int &p,int al,int len){
11         if(!p)p=++cnt;
12         if(al)v[p].push_back(al);
13         if(al==len)return;
14         build(trie[p][s[al]-a],al+1,len);
15     }
16     void bfs(){
17         for(int i=0;i<=25;++i)trie[0][i]=rt;q[1]=1;
18         for(int qh=1,qt=1;qh<=qt;++qh)for(int j=0;j<=25;++j)
19             if(trie[q[qh]][j])fail[q[++qt]=trie[q[qh]][j]]=trie[fail[q[qh]]][j];
20             else trie[q[qh]][j]=trie[fail[q[qh]]][j];
21         for(int i=1;i<=cnt;++i)for(int j=i;j;j=fail[j])
22             for(int k=0;k<v[j].size();++k)w[i].push_back(v[j][k]);
23     }
24 }pre,suc;
25 int main(){
26     scanf("%s",S);sL=strlen(S);
27     int n;scanf("%d",&n);
28     while(n--){
29         scanf("%s",s);int len=strlen(s);
30         pre.build(pre.rt,0,len);
31         reverse(s,s+len);
32         suc.build(suc.rt,0,len);
33     }pre.bfs();suc.bfs();
34     int p=1;
35     for(int i=0;i<sL;++i){
36         p=pre.trie[p][S[i]-a];
37         for(int k=0;k<pre.w[p].size();++k)pre_match[i-pre.w[p][k]+1]++;
38     }
39     reverse(S,S+sL);p=1;
40     for(int i=0;i<sL;++i){
41         p=suc.trie[p][S[i]-a];
42         for(int k=0;k<suc.w[p].size();++k)suc_match[sL-i-1+suc.w[p][k]]++;
43     }
44     for(int i=0;i<sL;++i)ans+=pre_match[i]*suc_match[i];//,printf("%lld %lld
",pre_match[i],suc_match[i]);
45     printf("%lld
",ans);
46 }
考场70分AC自动机匹配代码

大体思路就是,枚举两个串拼接起来后所在的位置。

对于S串的每一个位置,处理出向前能匹配几种后缀,向后能匹配几种前缀,然后对于每一个位置把这两个值相乘累加就是答案。

考虑怎么高效处理匹配。

不难发现如果某一个位置能够匹配串A的长度为L的后缀,那么它一定也能匹配其长度小于L的后缀。前缀同理。

先考虑怎么求出前缀。

最简单的想法就是枚举所有n的串,二分+哈希查看最长能匹配这个串多长的前缀。

然后我们又可以发现,如果你能匹配A串的长度为7的前缀和B串的长度为5的前缀,那么A和B的前5位就是完全相同的。

这样的话你对于AB两个串的前5位重复考虑其实就多余了。

意思就是,你能匹配的串数只与你所匹配的最长的那个串有关,知道能匹配的最长的串就能知道你能匹配几个串。

所以你把它放进一个trie里面。在建树的时候每路过一个节点就+1表示它能匹配的多了一个串。

然后再DFS一下,每个点都继承一下祖先链上的值(就是树上前缀和啦)。

再考虑一下,现在怎么找到最长的匹配串?

不能爆扫n个串了,复杂度过高。

于是我们还是二分答案,求出对应长度的串的哈希值,再看看trie树上有没有这个哈希值。

如果有就匹配上了。于是我们要知道trie上有哪些哈希值。

在DFS的时候顺便处理一下就好了,存在哈希表里,value值就存串数就好了。

然后对于后缀,会麻烦一些。大致的操作是在某些时候把串reverse过来,然后大部分都是同理的。

只不过哈希的正倒可能也反了,剩下的貌似区别不大。

实在不会就看码吧。。。

技术图片
 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<unordered_map>
 5 using namespace std;
 6 #define ull unsigned long long
 7 long long ans,pre_match[1000005],suc_match[1000005];char s[1000005],S[1000005];int sL;
 8 ull Hsh[200005],pw[200005];
 9 ull get_hash(int l,int r){return Hsh[r+1]-Hsh[l]*pw[r-l+1];}
10 struct trie{
11     int cnt,rt,trie[1000005][27];long long w[1000005];
12     unordered_map<ull,long long>M;
13     void build(int &p,int al,int len){
14         if(!p)p=++cnt;
15         if(p!=rt)w[p]++;
16         if(al==len)return;
17         build(trie[p][s[al]-a],al+1,len);
18     }
19     void dfs(int p,long long v,ull hsh,int dep=0){
20         if(!p)return;
21         w[p]+=v;M[hsh]=w[p];//printf("%d %d %d %llu
",p,w[p],v,hsh);
22         for(int i=0;i<=25;++i)dfs(trie[p][i],w[p],dep>=0?hsh+(i+1)*pw[dep]:hsh*29+i+1,dep>=0?dep+1:-1);
23     }
24 }pre,suc;
25 int main(){
26     scanf("%s",S);sL=strlen(S);pw[0]=1;
27     for(int i=1;i<=sL;++i)pw[i]=pw[i-1]*29;
28     int n;scanf("%d",&n);
29     while(n--){
30         scanf("%s",s);int len=strlen(s);
31         pre.build(pre.rt,0,len);
32         reverse(s,s+len);
33         suc.build(suc.rt,0,len);
34     }pre.dfs(pre.rt,0,0,-1);suc.dfs(suc.rt,0,0);
35     for(int i=0;i<sL;++i)Hsh[i+1]=Hsh[i]*29+S[i]-a+1;
36     pre.M[0]=0;suc.M[0]=0;
37     for(int i=0;i<sL;++i){
38         int l=0,r=i+1;
39         while(l<r-1)if(suc.M.find(get_hash(i-(l+r>>1)+1,i))!=suc.M.end())l=l+r>>1;else r=(l+r>>1)-1;
40         if(suc.M.find(get_hash(i-r+1,i))!=suc.M.end())l=r;else r=l;
41         suc_match[i+1]=suc.M[get_hash(i-l+1,i)];//printf("%d %d %llu
",i-l+1,i,get_hash(i-l+1,i));
42     }
43     for(int i=0;i<sL;++i){
44         int l=0,r=sL-i;
45         while(l<r-1)if(pre.M.find(get_hash(i,i+(l+r>>1)-1))!=pre.M.end())l=l+r>>1;else r=(l+r>>1)-1;
46         if(pre.M.find(get_hash(i,i+r-1))!=pre.M.end())l=r;else r=l;
47         pre_match[i]=pre.M[get_hash(i,i+l-1)];//("%d
",l);
48     }
49     for(int i=0;i<sL;++i)ans+=pre_match[i]*suc_match[i];//,printf("%lld %lld
",pre_match[i],suc_match[i]);
50     printf("%lld
",ans);
51 }
View Code

 

T3:

观察数据范围,发现皇后数超过5好像会有奇怪的事情发生。。。

手模一下:

k=1:每一个格子,n×m

k=2:任意一行,一列,或一条斜线。斜线的情况稍微复杂一点,可以考虑for循环枚举斜线的长度。

技术图片
1 if(n>m)n^=m^=n^=m;//为了方便强制n更小,注意下面的时候不要用反
2 ans=(n*C[m][k]+m*C[n][k])%mod;//行列
3 for(int i=1;i<n;++i)ans=(ans+4*C[i][k])%mod;//长度小于n的对角线
4 ans=(ans+2*(n+m-1-2*(n-1))*C[n][k])%mod;//长度等于n的对角线
View Code

其实任何k>=2的情况都是有行,列,斜线。在下面不再赘述。

k=3:多了一种等腰直角三角形的情况。有的是直角边平行于x/y轴,有的是斜边平行与x/y轴。

对于两种情况我们可以把三角形的三个顶点横纵坐标分别取min和max,这样我们就把它套进了一个矩形。

其中第一种情况时是一个正方形,第二种是一个长为2l-1宽为l-1的矩形。枚举边长就可以求了。

技术图片
1 if(k==3){
2     for(int l=2;l<=n;++l)ans=(ans+(n-l+1)*(m-l+1)*4)%mod;
3     for(int l=1;l<=m/2;++l)ans=(ans+up(n-l-l)*up(m-l)*2+up(n-l)*up(m-l-l)*2)%mod;
4 }
View Code

代码中的up函数是如果传的参数为正就返回原值,否则返回0。(防止负贡献的出现)

k=4:多了一种正方形的情况。以及一种直角三角形+斜边中点的情况(依旧要考虑斜边是否平行于坐标轴)。

第一种好做,依旧枚举边长,不再赘述。

第二种仍然把它套在正方形/矩形里,只不过枚举正方形的边长必须是2l-1的情况(否则斜边中点不是整点)。

技术图片
1 if(k==4){
2     for(int l=2;l<=n;++l)ans=(ans+(n-l+1)*(m-l+1))%mod;
3     for(int l=1;l<=n/2;++l)ans=(ans+up(n-l-l)*up(m-l-l))%mod;
4     for(int l=3;l<=n;l+=2)ans=(ans+(n-l+1)*(m-l+1)*4)%mod;//40
5     for(int l=1;l<=m/2;++l)ans=(ans+up(n-l-l)*up(m-l)*2+up(n-l)*up(m-l-l)*2)%mod;//60
6 }
View Code

k=5:多了一种十字的情况(还是要考虑是+还是x)

如果是x的话那么套在正方形里,边长是2l-1的情况。

如果是+的话其实和上面是完全一样的(代码写蠢了不要管我。。。你就当我是分开考虑的吧)

技术图片
1 if(k==5){
2     for(int l=3;l<=n;l+=2)ans=(ans+(n-l+1)*(m-l+1))%mod;
3     for(int l=1;l<=n/2;++l)ans=(ans+up(n-l-l)*up(m-l-l))%mod;
4 }
View Code

k>5时,只有所有皇后在一条直线上的情况。

给出考场上的80分暴力的完整代码。。。

技术图片
 1 #include<cstdio>
 2 #define int long long
 3 #define mod 300007
 4 int C[1005][1005];
 5 int up(int x){return x>0?x:0;}
 6 main(){
 7     for(int i=0;i<=1000;++i)C[i][0]=1;
 8     for(int i=1;i<=1000;++i)for(int j=1;j<=i;++j)C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
 9     int t,n,m,k,ans;scanf("%lld",&t);
10     while(t--){
11         scanf("%lld%lld%lld",&n,&m,&k);
12         if(n>m)n^=m^=n^=m;
13         ans=(n*C[m][k]+m*C[n][k])%mod;
14         for(int i=1;i<n;++i)ans=(ans+4*C[i][k])%mod;
15         ans=(ans+2*(n+m-1-2*(n-1))*C[n][k])%mod;
16         if(k==1)ans=n*m%mod;
17         if(k==3){
18             for(int l=2;l<=n;++l)ans=(ans+(n-l+1)*(m-l+1)*4)%mod;
19             for(int l=1;l<=m/2;++l)ans=(ans+up(n-l-l)*up(m-l)*2+up(n-l)*up(m-l-l)*2)%mod;
20         }
21         if(k==4){
22             for(int l=2;l<=n;++l)ans=(ans+(n-l+1)*(m-l+1))%mod;
23             for(int l=1;l<=n/2;++l)ans=(ans+up(n-l-l)*up(m-l-l))%mod;
24             for(int l=3;l<=n;l+=2)ans=(ans+(n-l+1)*(m-l+1)*4)%mod;//40
25             for(int l=1;l<=m/2;++l)ans=(ans+up(n-l-l)*up(m-l)*2+up(n-l)*up(m-l-l)*2)%mod;//60
26         }
27         if(k==5){
28             for(int l=3;l<=n;l+=2)ans=(ans+(n-l+1)*(m-l+1))%mod;
29             for(int l=1;l<=n/2;++l)ans=(ans+up(n-l-l)*up(m-l-l))%mod;
30         }
31         printf("%lld
",ans);
32     }
33 }
View Code

 

现在的问题就是,怎么把这些循环都拆成O(1)的。

首先第一个循环的就是在枚举斜线长度时的$sumlimits_{i=2}^{n-1} C_i^k$

高考数学。。。把它加上一个$C_2^{k+1}$之后就可以一直消了。

所以这个式子的值就是$C_n^{k+1} - C_2^{k+1}$

因为模数只有30万,所以就是在提示Lucas。

然后剩下的循环其实都是两个等差数列相乘的形式,概括为$sumlimits_{i=1}^x (ki+b) imes (pi+c)$

拆开,化简,得到:$(p imes k imes sumlimits_{i=1}^x i^2)+((ck+pb) imes sumlimits_{i=1}^x i)+x imes b imes c$

问题就在于$sumlimits_{i=1}^x i^2$与$sumlimits_{i=1}^x i$

后者你当然会。前者的答案是$frac{n imes (n+1) imes (2n+1)}{6}$

这样写两个sig1和sig2函数就能很方便了。

然后把每个循环都拆开,化一下式子就好了。

不要嫌麻烦。。。都是这么过来的

技术图片
 1 #include<cstdio>
 2 #define int long long
 3 #define mod 300007
 4 int fac[300009],inv[300009],invv[300009];
 5 int C(int b,int t){return t>b?0:fac[b]*inv[t]%mod*inv[b-t]%mod;}
 6 int Lucas(int b,int t){return t?Lucas(b/mod,t/mod)*C(b%mod,t%mod)%mod:1;}
 7 int up(int x){return x>0?x:0;}
 8 int sig1(int x){return x*(x+1)%mod*invv[2]%mod;}
 9 int sig2(int x){return x*(x+1)%mod*(2*x+1)%mod*invv[6]%mod;}
10 int min(int a,int b){return a<b?a:b;}
11 main(){
12     inv[0]=inv[1]=invv[1]=fac[0]=fac[1]=1;
13     for(int i=2;i<=mod;++i)fac[i]=fac[i-1]*i%mod,invv[i]=mod-mod/i*invv[mod%i]%mod,inv[i]=inv[i-1]*invv[i]%mod;
14     int t,n,m,k,ans;scanf("%lld",&t);
15     while(t--){
16         scanf("%lld%lld%lld",&n,&m,&k);
17         if(n>m)n^=m^=n^=m;int N=(n-1)%mod+1,M=(m-1)%mod+1,x;
18         ans=(N*Lucas(m,k)+M*Lucas(n,k))%mod;
19         ans=(ans+4*Lucas(n,k+1))%mod;
20         ans=(ans+2*(n+m-1-2*(n-1))%mod*Lucas(n,k))%mod;
21         if(k==1)ans=N*M%mod;
22         if(k==3){
23             ans=(ans+ ( N*M%mod*(N-1) -(N+M)%mod*sig1(N-1) +sig2(N-1) )*4 )%mod;
24             //for(int l=2;l<=n;++l)ans=(ans+(n-l+1)*(m-l+1)*4)%mod;
25             x=(min(m,n/2)-1)%mod+1;
26             ans=(ans+ ( N*M%mod*x + 2*sig2(x) - (N+M*2)*sig1(x) )*2 )%mod;
27             x=(min(n,m/2)-1)%mod+1;
28             ans=(ans+ ( N*M%mod*x + 2*sig2(x) - (M+N*2)*sig1(x) )*2 )%mod;
29             //for(int l=1;l<=m/2;++l)ans=(ans+up(n-l-l)*up(m-l)*2+up(n-l)*up(m-l-l)*2)%mod;
30         }
31         if(k==4){
32             ans=(ans+ N*M%mod*(N-1) -(N+M)%mod*sig1(N-1) +sig2(N-1) )%mod;
33             //for(int l=2;l<=n;++l)ans=(ans+(n-l+1)*(m-l+1))%mod;
34             x=n/2%mod;
35             ans=(ans+ N*M%mod*x + 4*sig2(x) - (N+M)*sig1(x)*2 )%mod;
36             //for(int l=1;l<=n/2;++l)ans=(ans+up(n-l-l)*up(m-l-l))%mod;
37             x=(n-1)/2%mod;
38             ans=(ans+ ( N*M%mod*x + 4*sig2(x) -(N+M)*sig1(x)*2 )*4 )%mod;
39             //for(int l=3;l<=n;l+=2)ans=(ans+(n-l+1)*(m-l+1)*4)%mod;
40             x=(min(m,n/2)-1)%mod+1;
41             ans=(ans+ ( N*M%mod*x + 2*sig2(x) - (N+M*2)*sig1(x) )*2 )%mod;
42             x=(min(n,m/2)-1)%mod+1;
43             ans=(ans+ ( N*M%mod*x + 2*sig2(x) - (M+N*2)*sig1(x) )*2 )%mod;
44             //for(int l=1;l<=m/2;++l)ans=(ans+up(n-l-l)*up(m-l)*2+up(n-l)*up(m-l-l)*2)%mod;
45         }
46         if(k==5){
47             x=(n-1)/2%mod;
48             ans=(ans+ N*M%mod*x + 4*sig2(x) -(N+M)*sig1(x)*2 )%mod;
49             //for(int l=3;l<=n;l+=2)ans=(ans+(n-l+1)*(m-l+1))%mod;
50             x=n/2%mod;
51             ans=(ans+ N*M%mod*x + 4*sig2(x) - (N+M)*sig1(x)*2 )%mod;
52             //for(int l=1;l<=n/2;++l)ans=(ans+up(n-l-l)*up(m-l-l))%mod;
53         }
54         printf("%lld
",(ans+mod)%mod);
55     }
56 }
这份代码里有原始式子和新式子的一一对应(原始式子注释在新式子后面)

因为教练说太丑,所以又稍改了一下。

技术图片
 1 #include<cstdio>
 2 #define int long long
 3 #define P 300007
 4 int fac[300009],inv[300009],invv[300009];
 5 int C(int b,int t){return t>b?0:fac[b]*inv[t]%P*inv[b-t]%P;}
 6 int Lucas(int b,int t){return t?Lucas(b/P,t/P)*C(b%P,t%P)%P:1;}
 7 int up(int x){return x>0?x:0;}
 8 int sig1(int x){return x*(x+1)%P*invv[2]%P;}
 9 int sig2(int x){return x*(x+1)%P*(2*x+1)%P*invv[6]%P;}
10 int min(int a,int b){return a<b?a:b;}
11 main(){
12     inv[0]=inv[1]=invv[1]=fac[0]=fac[1]=1;
13     for(int i=2;i<=P;++i)fac[i]=fac[i-1]*i%P,invv[i]=P-P/i*invv[P%i]%P,inv[i]=inv[i-1]*invv[i]%P;
14     int t,n,m,k,ans;scanf("%lld",&t);
15     while(t--){
16         scanf("%lld%lld%lld",&n,&m,&k);
17         if(n>m)n^=m^=n^=m;int N=(n-1)%P+1,M=(m-1)%P+1,x;
18         ans=(N*Lucas(m,k)+M*Lucas(n,k))%P;
19         ans=(ans+4*Lucas(n,k+1))%P;
20         ans=(ans+2*(n+m-1-2*(n-1))%P*Lucas(n,k))%P;
21         if(k==1)ans=N*M%P;
22         if(k==3){
23                                     ans=(ans+(N*M%P*(N-1)-(N+M)%P*sig1(N-1)+sig2(N-1))*4)%P;
24             x=(min(m,n/2)-1)%P+1;    ans=(ans+(N*M%P*x+2*sig2(x)-(N+M*2)*sig1(x))*2)%P;
25             x=(min(n,m/2)-1)%P+1;    ans=(ans+(N*M%P*x+2*sig2(x)-(M+N*2)*sig1(x))*2)%P;
26         }
27         if(k==4){
28                                     ans=(ans+N*M%P*(N-1)-(N+M)%P*sig1(N-1)+sig2(N-1))%P;
29             x=n/2%P;                ans=(ans+N*M%P*x+4*sig2(x)-(N+M)*sig1(x)*2)%P;
30             x=(n-1)/2%P;            ans=(ans+(N*M%P*x+4*sig2(x)-(N+M)*sig1(x)*2)*4)%P;
31             x=(min(m,n/2)-1)%P+1;    ans=(ans+(N*M%P*x+2*sig2(x)-(N+M*2)*sig1(x))*2)%P;
32             x=(min(n,m/2)-1)%P+1;    ans=(ans+(N*M%P*x+2*sig2(x)-(M+N*2)*sig1(x))*2)%P;
33         }
34         if(k==5){
35             x=(n-1)/2%P;            ans=(ans+N*M%P*x+4*sig2(x)-(N+M)*sig1(x)*2)%P;
36             x=n/2%P;                ans=(ans+ N*M%P*x+4*sig2(x)-(N+M)*sig1(x)*2)%P;
37         }
38         printf("%lld
",(ans+P)%P);
39     }
40 }
这份代码在gedit等宽字体下效果不错(updated)

 (因为博客园会把制表符弄成空格所以没有对齐,看着不顺眼的手动自己敲两下啦)

一定要注意n和m已经到了long long级别,要及时取模

以上是关于[考试反思]1022csp-s模拟测试82:奇异的主要内容,如果未能解决你的问题,请参考以下文章

[考试反思]1002csp-s模拟测试57:平庸

[考试反思]1006csp-s模拟测试61:休止

[考试反思]1110csp-s模拟测试108:消遣

[考试反思]1004csp-s模拟测试59:惊醒

[考试反思]1112csp-s模拟测试111:二重

[考试反思]1012csp-s模拟测试70:盘旋