zoukankan      html  css  js  c++  java
  • [考试反思]1219省选模拟3: 释怀

    有些东西渐渐就远去了。。。不必挂念。

    只有时间才拥有如此力量。我们能做的,唯有释怀。

    这次的题格外顺手,大概是我的强项了。

    但是考得还是不够好吧。。。感觉可以更高的

    今天迎来了学了OI一年多比较重要的一个成就:(虽说是在考后)

    $AC1000$道题!还是挺不容易的。

    第$1000$道题是今天的T3。大部分是自己思考的,题也不是很简单,挺好的。

    挺过了联赛,现在想要等到下一次整百,可能就要到3月份了。

    我还能在OI多久呢?

    回到这场考试。

    T1的数据范围只有$10000$,让我想起了$ALO$那道题$50000$数据被我随机化$AC$。

    然而这题可以构造毒瘤数据所以正确率没有那么高,但是除了随机化啥也不会写。

    于是拿一个$60$走人(其实很满意了)。

    T2一眼秒,$AC$自动机+拓扑$dp$,当然需要$tarjan$。

    $tarjan$?啊。。。好像不会写了,弃了。

    然后正解真的就是这个,自闭。。。

    T3其实是最先开始做的题,因为测试1里做的那道题有提到积性函数,然后就想了一下这题的这玩意显然也就是积性函数。

    然后$P=2$的就很好说了。很快写完。然后又研究了一下$P=2017$,也就证明了结论但是复杂度不对,一共$80$分滚粗。

    这点分拿到之后就啥都没干了。T2写了个$AC$自动机但是没有$tarjan$没有用。然后就不知道该干什么了。

    $tarjan$这种东西吧。。。

    至少现在,应该是真的会了。。。幸亏联赛没考不然就退役了。

    T1:好题

    题意:树上每个点有色,求最小联通块含有k种颜色。$ n le 10000,k le 5$,颜色$le$n

    $k le 5$一定是突破口。但是颜色那么多怎么办?

    随机化。把所有的颜色分成k类,同类的视为一种颜色。

    然后树上状压$dp$就可以得到含有k类颜色的最小联通块。

    显然找到的方案一定是合法的,但不一定是最优的。

    考虑最优方案的联通块所包含的$k$种颜色,你能找出这个解当且仅当这k种颜色被恰好分为了不同的$k$类。

    合法的总方案是$k!$个(内部顺序任意,所以是排列)。而对这k中颜色分类的总方案数就是$k^k$。

    所以单次随机化得到正解的概率是$frac{k!}{k^k}$。在$k=5$时大约是$4 \%$

    这样的话,只要进行$50$次随机化,正确率就已经高达$87 \% $了。然而你让它一直$clock$跑下去肯定是不会错的。

    单次随机化的复杂度是$O(3^kn)$。然而我懒得写枚举子集了,于是单次复杂度变为$O(4^kn)$。

    还是可以过的。

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define S 10003
     4 int n,k,fir[S],l[S<<1],to[S<<1],co[S],ec,cnt,c,ans=S,tans,sz[S],dp[S][33],re[S],Co[S];
     5 void dfs(int p,int fa){
     6     dp[p][1<<co[p]]=1;
     7     for(int i=fir[p];i;i=l[i])if(to[i]!=fa){
     8         dfs(to[i],p);
     9         for(int a=1;a<1<<k;++a)for(int b=1;b<1<<k;++b)dp[p][a|b]=min(dp[p][a|b],dp[p][a]+dp[to[i]][b]);
    10     }ans=min(ans,dp[p][(1<<k)-1]);
    11 }
    12 main(){
    13     scanf("%d%d",&n,&k);
    14     for(int i=1;i<=n;++i)scanf("%d",&Co[i]);
    15     for(int i=1,x,y;i<n;++i)scanf("%d%d",&x,&y),
    16         l[++ec]=fir[x],fir[x]=ec,to[ec]=y,
    17         l[++ec]=fir[y],fir[y]=ec,to[ec]=x;
    18     while(clock()<699999){
    19         for(int i=1;i<=n;++i)re[i]=rand()%k;
    20         for(int i=1;i<=n;++i)co[i]=re[Co[i]];
    21         memset(dp,0x3f,sizeof dp);dfs(1,0);
    22     }printf("%d
    ",ans);
    23 }
    代码真好写。。。

    T2:坏题

    题意:给定$n$个长度不超过$10$的串,字符集大小为$t$。求有多少种两端都无限长的串,不与给定串匹配。

    两串相同当且仅当一个串下标全部$+k$后完全匹配。$n le 1000 ,t le 6$

    多串匹配,八成$AC$自动机。

    题意就是$AC$自动机上,从环出发到环结束,有多少方案。

    对于非简单环,它就可以乱走了,方案数无穷。

    对于一个环走到一个环再走到一个环,中间的环走几次是任意的,方案数也无穷。

    看到一堆环环环的,那就写$tarjan$缩一下呗。然而我不会

    对于简单环的判定,条件很简单:如果你是$n$个点的强联通分量,那么你就一定有至少$n$条边。

    如果是恰好$n$条边,那么构造就明确了一定是一个简单环。

    否则多于$n$条边的话,你一定可以在图里提取出一个环,而在这环以外的边会使这个环上的点形成新的路径。

    因为一个环已经强联通了,再连新的边一定会与环上对应两点的两条路径之一形成新的环,从而它就不是简单环了。

    所以$tarjan$缩完之后,跑拓扑做$dp$就可以了。

    注意在$tarjan$时单个点无自环也会被判定为强联通分量,在$dp$时需要特殊处理。

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define S 333333
     4 int c[S][7],fail[S],q[S],nt[S],t,qt,n,rt,pc,tim,scc,tp,ed[S],pt[S];
     5 int dfn[S],low[S],bl[S],ins[S],s[S],fir[S],l[S],to[S],ec;char ss[11];
     6 int FIR[S],L[S],TO[S],deg[S],EC,ans,dp[S][3];
     7 void link(int a,int b){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;}
     8 void Link(int a,int b){L[++EC]=FIR[a];FIR[a]=EC;TO[EC]=b;deg[b]++;}
     9 void insert(int&p,int al){
    10     if(!p)p=++pc;
    11     if(!ss[al]){nt[p]=1;return;}
    12     insert(c[p][ss[al]-'a'],al+1);
    13 }
    14 void bfs(){
    15     q[1]=rt;for(int i=0;i<t;++i)c[0][i]=rt;
    16     for(int h=1,qt=1;h<=qt;++h)for(int i=0;i<t;++i)
    17         if(c[q[h]][i])fail[q[++qt]=c[q[h]][i]]=c[fail[q[h]]][i];
    18         else c[q[h]][i]=c[fail[q[h]]][i];
    19 }
    20 void tarjan(int p){
    21     dfn[p]=low[p]=++tim;s[++tp]=p;ins[p]=1;
    22     for(int i=fir[p];i;i=l[i])if(!dfn[to[i]])tarjan(to[i]),low[p]=min(low[p],low[to[i]]);
    23         else if(ins[to[i]])low[p]=min(low[p],low[to[i]]);
    24     if(dfn[p]==low[p]){
    25         bl[p]=++scc;ins[p]=0;
    26         while(s[tp]!=p)ins[s[tp]]=0,bl[s[tp--]]=scc;tp--;
    27     }
    28 }
    29 int main(){
    30     scanf("%d%d",&t,&n);
    31     for(int i=1;i<=n;++i)scanf("%s",ss),insert(rt,0);
    32     bfs();for(int i=1;i<=pc;++i)for(int j=i;j;j=fail[j])nt[i]|=nt[j];
    33     for(int i=1;i<=pc;++i)for(int j=0;j<t;++j)if(!nt[i]&&!nt[c[i][j]])link(i,c[i][j]);
    34     tarjan(1);
    35     for(int i=1;i<=pc;++i)pt[bl[i]]++;
    36     for(int i=1;i<=pc;++i)for(int j=fir[i];j;j=l[j])if(bl[i]==bl[to[j]])ed[bl[i]]++;
    37         else Link(bl[i],bl[to[j]]);
    38     for(int i=1;i<=scc;++i)if(pt[i]!=ed[i]&&pt[i]>1)return puts("-1"),0;
    39     for(int i=1;i<=scc;++i)if(!deg[i])q[++qt]=i;
    40     for(int i=1;i<=scc;++i)if(pt[i]==ed[i])dp[i][0]=1;
    41     for(int i=1;i<=scc;++i)if(pt[i]==ed[i])ans++;
    42     for(int h=1;h<=qt;++h){
    43         for(int i=FIR[q[h]];i;i=L[i]){
    44             deg[TO[i]]--;
    45             if(!deg[TO[i]]) q[++qt]=TO[i];
    46             if(pt[TO[i]]==ed[TO[i]]) dp[TO[i]][1]+=dp[q[h]][0];
    47             else dp[TO[i]][1]+=dp[q[h]][1],dp[TO[i]][0]+=dp[q[h]][0];
    48         }
    49         if(pt[q[h]]==ed[q[h]])ans+=dp[q[h]][1];
    50     }
    51     printf("%d
    ",ans);
    52 }
    View Code

    T3:不好不坏题

    题意:求$n$以内的所有约数和是$P$倍数的数之和。$n le 10^{10}$,$P$为$2$或$2017$。

    可能是因为我现在AC了所以我觉得它真是挺好的一道题。。。做得怪累的。

    约数和是积性函数。所以我们拆解质因数进行考虑。

    首先想$P=2$吧。

    分解质因数后,只要有一种质因子的贡献是偶数,那么它最终的质因数之和就是偶数,就会贡献答案。

    「只要有一种就」这种限制条件不是很好考虑,来考虑相反的「一种都没有」

    那就要求每种质因子的约数和都是奇数,这种数不会贡献答案。

    对于同种质因子$p^e$它的约数和是$sumlimits_{i=0}^{e} p^i$。除非$p=2$,这里每一项都是奇数。

    那么只要有偶数项就好了,也就是质因子的指数必须是偶数。

    那么质因子指数都是偶数的话,那么。。。不就是个平方数吗。。?

    所以所有的平方数都不会贡献答案。

    不太对。上面有一句「除非$p=2$」,还没有考虑呢。

    发现一个数不管有几个2它的因子和的奇偶性都不会变。

    所以其实你不需要考虑$2$的次数,也就是$2$出现了奇数次也可以。那么就是平方数的$2$倍也不会贡献答案。

    (不必再考虑$4$倍及更高,$4$倍不就已经就是平方数了吗,已经考虑过了)

    然后这样就拿到了$P=2$的$50$分。接下来是$P=2017$。

    线筛可以做,但是这做法显然没有扩展性(扩展成洲阁筛???)

    首先我们惊奇的发现$2017$是一个良好的质数。

    类似于上面的思路,还是分解质因数,如果任意一个质因数的贡献是$2017$的倍数,那么这个数就会产生贡献。

    因为$2017$是质数所以不会出现由多个质因子加起来才能拼凑出$2017$的情况。

    先考虑指数为$1$,那么其实就是$p+1 equiv 0(mod 2017)$,这种东西不会筛啊。

    枚举有一定技巧啊,它肯定是$2017$的整倍数$-1$啊,而且它还必须是奇数。

    那么枚举就是以$4033$为首项,$4034$为公差。

    这样的枚举量是$frac{n}{4034}$的,对于最大的数据这个数大约是$2.5 imes 10^6$。

    然后。。。然后我只会根号筛了。

    考虑指数为$2$,是$(p^2+p+1)%2017==0$。受到$n$的限制$p$不会超过$sqrt{n}$,直接枚举就好了。

    同理指数为$3$,打表发现其实只有$229$这一个$p$满足条件。在代码里我选择特殊处理了。

    对于这些质数的次幂,它们的任意倍数的约数和都是$2017$的倍数,都会贡献答案,等差数列求和就好。

    然后,到此为止,你就能得到$80$分的好成绩了。

    但是,目前为止,你不仅跑的慢,在大数据下答案还是错的!

    为什么?

    举个例子,最小的$1$次幂质数是$12101$。数据范围超过$10^8$时,就可能会出现$12101^2$这种东西。

    你刚才累加答案的时候,是把$12101$的倍数都加进去了,所以也会把这个数加进去。

    但是积性函数只有在互质时才能满足乘法,所以你得到$12101^2$的质因数之和是错的。。。

    二次幂与三次幂同理。但是最小的二次幂是$2311$,它的$3$次幂超过了最大的$n$。所以不必考虑。

    唯一的三次幂$229$的$4$次幂还是有可能在n以内的,一定要记得考虑!

    然后跨次幂相撞是不存在的,它们都超过了$n$的范围。

    所以只需要考虑一次幂数的平方和$229$的$4$次方即可。

    容斥解决。

    但是到了这里答案是对的,但是在最大数据下运行时间在$15s$以上。。。复杂度就卡在那个根号筛了。

    然后其实需要的功能就是筛质数,然后看过数学一本通的我想到了$Miller   Rabin$。然而我肯定不会写啊。

    至少考后会了,而且也学会了快速乘。

    然后抄一下$LNC$大神的$Miller   Rabin$板子就好了。

    等我彻底理解了就补充一下$Miller Rabin$与快速乘的草率讲解。

    先说快速乘吧,它就是用来处理模数在$long   long$级别的乘法。

    考虑模法的实际含义$a \% b= a - frac{a}{b} imes b$

    而这里的问题是$xy mod b $。

    首先$frac{xy}{b}$那一项好说,因为最后结果不大,所以直接用$double$算不会炸精。

    然后$xy$与$frac{xy}{b} imes b$的差值不超过$b$,也就是不会超过$long long$的范围,所以直接自然溢出。

    你并不在意溢出的那些位,因为它差值不大,高位本来就不存在。

    所以最后的流程就是$xy$乘法用$unsigned long long$自然溢出,减去用$double$算的除法强制转$unsigned long long$后乘$b$也自然溢出。

    把两个自然溢出的数相减,就得到了最后的余数。

    而有一种东西叫做$GongKai$优化(名字很高大上有没有?$cbx$发明的)。

    就是如果做乘法的两个数如果都没有超过$4 imes 10^9$,就用$1ull$做通常乘法和模法。

    看起来非常蠢,但是强制转换和$double$类型很慢,在这道题里两种做法的常数差了三分之一,还是很明显的。

    而关于$Miller Rabin$,其实它大概是基于结论的。

    我们知道费马小定理:$a^p equiv a(mod p)$,对于$p$是质数。

    而它的逆命题虽然并不是正确的,但是它的正确率很高。

    我们取$a=2$的话,这个结论就对于$p le 300$判断无误了。

    如果你只用$a=2$来判定$p$是否为质数,那么你的正确率大约是$99.988 \% $。

    于是再用一下$a=3$来进行同理判定,正确率就提升到了$99.9975 \% $。每$40000$次尝试错误$1$次。

    有一类$Carmicheal$数,如$561$,它对于所有的$a<p$都可以通过测试,然而它确是一个合数。用裸的$Miller Rabin$无论如何也会错判。

    其实这种数还是很少的,出现概率大约$2 imes 10^{-6}$。

    但是只是这样对于$OI$的巨大数据范围,一点差错都是致命的,所以考虑优化。

    引出另一个结论:如果有$x^2 equiv 1 (mod p)$且p是质数时,$(x-1)(x+1) equiv 0 (mod p)$

    而因为$p$是质数,所以$(x-1)$与$(x+1)$中一定有一个数是$p$的倍数,否则它们相乘也不可能含有因子$p$。

    那么$x$在模$p$意义下,就是$1$或$p-1$了。

    那么接下来就是算法流程,加入我们要检查$p$是否为质数。

    我们首先提出$p-1$里所有的因子$2$,设一共$r$个,其余因子的积是$y$。

    随机选出一个$z$在$[1,p-1]$范围内,将$z$做$y$次幂。

    因为有$a^{p-1} equiv 1 (mod p)$,(如果p是质数的话),那么我们就得到了$a^{frac{p-1}{2}}$这个数的平方同余于$1$,满足所说的结论的式子。

    接下来因为它有$r$个$2$,所以它可以开$r$次根,把$1$开成$1$或$-1$。

    然而为了方便,我们从最里面往外走,不是开根,而是平方。(因为开跟你也不会做)

    也就是从$z^y$开始,不断进行平方,如果由非$p-1$或$1$的数平方得到了$1$,那么就判定$p$不是质数。

    最后,退出前,如果你平方了$r$次,也就是$z^{p-1} equiv 1 (mod p )$不成立,那么$p$也不是质数。

    如果上述条件都没有把它弄掉,那么它是质数的概率就很大了。

    然而只随机了一个$z$并没有说服力,再多做几遍,如果全部通过,那么就当它是质数吧!

    然而因为多次调用快速乘与快速幂,所以常数巨大。

    于是我们用$2$,$3$,$5$,$7$,$11$等几个小质数除一下,如果整除了那么肯定就是合数了。提前跳出,常数减半!

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define mod 1000000007
     4 #define ll long long
     5 #define ull unsigned int
     6 #define S 30000005
     7 int P,pc,nc;bool np[S];ll num[S],pr[S],ans,n;const ll U=4000000000;
     8 ll mul(ll x,ll y,ll p){if(x<U&&y<U)return 1ull*x*y%p;ll z=(double)x/p*y,res=(unsigned ll)x*y-(unsigned ll)z*p;return (res+p)%p;}
     9 ll pow(ll b,ll t,ll M,ll a=1){for(;t;t>>=1,b=mul(b,b,M))if(t&1)a=mul(a,b,M);return a;}
    10 bool isprime(ll x){
    11     ll y=x-1,k=0;
    12     if(x%3==0)return 0;
    13     if(x%5==0)return 0;
    14     if(x%7==0)return 0;
    15     if(x%11==0)return 0;
    16     while(!(y&1))++k,y>>=1;
    17     for(int i=1;i<=2;++i){
    18         ll z=rand()%(x-1)+1,d;z=pow(z,y,x);
    19         for(int j=1;j<=k;++j){
    20             d=mul(z,z,x);
    21             if(d==1&&z!=1&&z!=x-1)return 0;
    22             z=d;
    23         }
    24         if(d!=1)return 0;
    25     }return 1;
    26 }
    27 ll C(ll x){ll t=n/x%mod;return t*(t+1)/2%mod*x%mod;}
    28 main(){
    29     scanf("%lld%d",&n,&P);
    30     if(P==2){ans=(n%mod)*(n%mod+1)/2%mod;
    31         for(ll i=1;i*i<=n;++i)ans=(ans-i*i)%mod;
    32         for(ll i=1;i*i*2<=n;++i)ans=(ans-i*i*2)%mod;
    33     }else{
    34         for(int i=2;i<=100000;++i){
    35             if(!np[i])pr[++pc]=i;
    36             for(int j=1;j<=pc&&i*pr[j]<=100000;++j)
    37                 if(i%pr[j]==0){np[i*pr[j]]=1;break;}
    38                 else np[i*pr[j]]=1;
    39         }
    40         for(ll i=4033;i<=n;i+=4034)if(isprime(i))ans=(ans+C(i))%mod,num[++nc]=i;
    41         for(int i=1;i<=nc&&num[i]<=n/10121;++i)for(int j=1;j<=i&&num[j]*num[i]<=n;++j)ans=(ans-C(num[i]*num[j]))%mod;
    42         for(int j=1;1ll*pr[j]*pr[j]<=n;++j)if((1+pr[j]+1ll*pr[j]*pr[j])%2017==0)ans=(ans+C(1ll*pr[j]*pr[j]))%mod;
    43         ans=(ans+C(229*229*229)-C(229ll*229*229*229))%mod;
    44     }printf("%lld
    ",(ans+mod)%mod);
    45 }
    好题啊
  • 相关阅读:
    微信公众号开发的经验与坑
    微信公众号开发经验总结
    微信H5中禁止分享好友及分享到朋友圈的方法
    js 实现纯前端将数据导出excel两种方式,亲测有效
    JavaScript数组的一些方法、数学对象、定时器
    几种动态轨迹可视化效果实现方案-echarts、mapv、deck.gl
    前端大牛的博客收集
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
  • 原文地址:https://www.cnblogs.com/hzoi-DeepinC/p/12069900.html
Copyright © 2011-2022 走看看