[考試反思]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\n",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\n",ans);
52 }
View Code

 

T3:不好不壞題

題意:求$n$以內的所有約數和是$P$倍數的數之和。$n \le 10^{10}$,$P$為$2$或$2017$。

可能是因為我現在AC了所以我覺得它真是挺好的一道題。。。做得怪累的。

約數和是積性函數。所以我們拆解質因數進行考慮。

首先想$P=2$吧。

分解質因數后,只要有一種質因子的貢獻是偶數,那么它最終的質因數之和就是偶數,就會貢獻答案。

「只要有一種就」這種限制條件不是很好考慮,來考慮相反的「一種都沒有」

那就要求每種質因子的約數和都是奇數,這種數不會貢獻答案。

對於同種質因子$p^e$它的約數和是$\sum\limits_{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 \times 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} \times b$

而這里的問題是$xy \ mod \ b $。

首先$\frac{xy}{b}$那一項好說,因為最后結果不大,所以直接用$double$算不會炸精。

然后$xy$與$\frac{xy}{b} \times b$的差值不超過$b$,也就是不會超過$long \ long$的范圍,所以直接自然溢出。

你並不在意溢出的那些位,因為它差值不大,高位本來就不存在。

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

把兩個自然溢出的數相減,就得到了最后的余數。

而有一種東西叫做$GongKai$優化(名字很高大上有沒有?$cbx$發明的)。

就是如果做乘法的兩個數如果都沒有超過$4 \times 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 \times 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\n",(ans+mod)%mod);
45 }
好題啊


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM