這博客咕了9天,再咕沒機會寫了XD
然而考得依舊不怎麼樣。
$T1$的暴力是個記搜類似物,非常開心的寫,本機跑的出$70$的點,於是很開心的交了。
然後因爲是期望題實在不會手模,過了看似不小的樣例然後就沒再管正確性,然後爆掉了。
$T2$是個大分類討論題,但是由於自己思路並不清晰,所以還是$WA$掉了
按照慣例$T3$是神仙題全場最高$15$,也就沒有寫。然後就炸了。
T1:隨機除法(div)
大意:多測。給你一個$n$,並給出所有質因子$p_i$,每次將$n$除掉它所有因子中的隨機一個。求期望多少次之後會變成$1$。$p_i \le 10^6,n \le 10^{24},T \le 10^5$
首先肯定會想把這個$n$分解爲$p_i^{e_i}$的形式。但是$n$挺大的,要寫高精?
事實上,$10^{24}$這個數據範圍給的還是比較合適的,$\sqrt{10^{24}}=10^{12},10^{12} \times 10^6 < MAX\ long\ long$
所以只要拿$10^{12}$做進制數寫個類似高精的東西,每次乘除都不會爆掉$long\ long$。還是挺好寫的。詳見代碼。
然後回到這道題上來,我們不難發現執行次數與$p_i$無關而只與$e_i$有關。這是指數上的東西,我們大膽猜測不會很多。
集合大小最大也就$18$。所有無序狀態數也不到$2 \times 10^5$。
不難發現所有集合都可以用$2^{e_1} \times 3^{e_2} \times 5^{e_3} \times 7^{e_4} \times ...(e_1 \ge e_2 \ge e_3 \ge e_4 \ge ...)$的形式表示出來。
狀態數也不多所以直接搜出來,每次走到的都一定是一個沒有被走到過的集合。
然後我們考慮怎麼求出每種集合的$dp$值。除了自環之外,轉移一定是$DAG$。每種質因子都有可能減少或不變。
這好像就是高維前綴和的形式。然後一個比較經典但是總被遺忘的做前綴和的方法就是:依次對每一維做前綴和。
具體而言就是,先讓第一維做前綴和剩下維不變,然後用得到的數組給第二維做前綴和,此時得到的就是前兩維都做過前綴和的數組。一直做下去就好。
還有一個問題是怎麼快速的用一個集合得到一個數組下標。不難想到哈希。但是這道題需要支持 某一維$-1$之後還要保證元素無序(小的必須在前面)
如果每次都是$-1$然後$sort$就會$TLE$。所以我們需要一個更優的哈希函數滿足:無序,可單位減。
這裏$skyh$大神提供了一個巧妙的方法:任取一個常數$c$,然後構造$hash=\sum c^{e_i}$。顯然滿足了以上性質。
於是本題得到了解決。時間複雜度$O(2 \times 10^5 \times 18 + T\ log\ n)$
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 #define ull unsigned ll 5 const int p[]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71},S=3e6+7,mod=1e9+7,s=2e5; 6 const ll Mod=1000000000000ll; 7 int cnt,f[s][20],v[s][20],dc[s],dvs[20],dp[s]; ull pw[90],Hsh[s]; 8 struct hash_map{ 9 int fir[S],l[S],v[S],ec;ull to[S]; 10 int&operator[](ull x){int r=x%S; 11 for(int i=fir[r];i;i=l[i])if(to[i]==x)return v[i]; 12 l[++ec]=fir[r];fir[r]=ec;to[ec]=x;return v[ec]=-1; 13 } 14 }M; 15 int qp(int b,int t=mod-2,int a=1){for(;t;t>>=1,b=1ll*b*b%mod)if(t&1)a=1ll*a*b%mod;return a;} 16 void sch(long double n,int pc,int lt,int f,int P){ 17 for(int i=0;i<19;++i)Hsh[P]+=pw[v[P][i]]; 18 M[Hsh[P]]=P; 19 for(int i=1;i<=lt&&n/p[pc]>1;++i){ 20 v[++cnt][pc]=i; dc[cnt]=dc[P]*(i+1); 21 for(int j=0;j<pc;++j)v[cnt][j]=v[P][j]; 22 sch(n/=p[pc],pc+1,i,P,cnt); 23 } 24 } 25 int mo(int x){return x>=mod?x-mod:x;} 26 int main(){ 27 freopen("div.in","r",stdin);freopen("div.out","w",stdout); 28 for(int i=pw[0]=1;i<90;++i)pw[i]=pw[i-1]*23; dc[1]=cnt=1; 29 sch(1e24,0,100,0,1); 30 for(int i=2;i<=cnt;++i){ 31 for(int j=18;~j;--j)if(v[i][j])f[i][j]=mo(f[i][j+1]+f[M[Hsh[i]-pw[v[i][j]]+pw[v[i][j]-1]]][j]); 32 dp[i]=(f[i][0]+dc[i])*1ll*qp(dc[i]-1)%mod; 33 for(int j=0;j<=18;++j)f[i][j]=mo(f[i][j]+dp[i]); 34 } 35 char N[28];int m;ll hn,ln; 36 while(scanf("%s%d",N,&m)==2){ 37 ln=hn=0; for(int i=0;i<19;++i)dvs[i]=0; 38 for(int i=0;N[i];++i)ln=ln*10+N[i]-48,hn=hn*10+ln/Mod,ln%=Mod; 39 for(int i=0,p;i<m;++i){ 40 scanf("%d",&p); 41 while(((hn%p)*Mod+ln)%p==0)ln+=hn%p*Mod,hn/=p,ln/=p,dvs[i]++; 42 } 43 sort(dvs,dvs+m,[](int a,int b){return a>b;}); 44 ull hsh=0; 45 for(int i=0;i<19;++i)hsh+=pw[dvs[i]]; 46 printf("%d\n",dp[M[hsh]]); 47 } 48 }
T2:炮塔(tower)
大意:給定一個由炮塔,干擾器,空地 組成的序列,你最開始在位置$1$。如果你在位置$x$且$x-1,x+1$都不是干擾器你就掛了。
你每次可以向一個方向走一步,或者撿起當前位置的干擾器,或者如果手裏有干擾器的話可以放在當前位置。求整個過程中你最多持有多少干擾器。$|S| \le 10^5$
大型分類討論。
如果當前格子是干擾器,那就撿起來唄。(如果需要放,那再說)
如果當前格子是空地,那就往前走唄。
否則當前格子一定是炮臺,繼續分類討論:
如果下一個格子是干擾器,那就往前走唄。
(你會把那個干擾器撿起來,以前如果有需要手持一個干擾器才能撿的,以後就需要兩個了,因爲你把干擾器撿起來了,跨過炮臺還需要一個)
如果下一個格子是空地,那就在前一個格子放下一個干擾器然後往前走唄。
(如果沒幹擾器了就不走了,否則如果你以後手裏有其它至少一個干擾器,就可以回來把這個和前面的撿起來)
再否則,下一個格子一定是炮臺,繼續再分類討論:
如果下下個格子是干擾器,那麼就可以在前一個格子放一個干擾器然後繼續往前走,撿起對面的干擾器,對各個變量沒有影響。
否則你一定走不過去。結束。
始終維護四個變量:當前持有的,最大持有的,如果手裏有一個能拿到的,如果手裏有兩個能拿到的。然後就可以寫了。
思路還是應該清晰一些,到文化課上也會有用的。
1 #include<bits/stdc++.h> 2 using namespace std; 3 char s[6666666];int n,v1,v2,c,mx; 4 int main(){freopen("tower.in","r",stdin);freopen("tower.out","w",stdout);int t;cin>>t;while(t--){ 5 scanf("%s",s);n=strlen(s);s[n++]='#';s[n++]='#';s[n++]='#'; 6 for(int i=0;;++i){ 7 if(s[i]=='*')c++; 8 else if(s[i]=='.'); 9 else if(s[i+1]=='*')v2+=v1,v1=0; 10 else if(s[i+1]=='.'){if(c)c--,v1++;else break;} 11 else if(s[i+2]=='*'){if(c)i++,c--;else break;} 12 else break; 13 if(c>=1)c+=v1,v1=0; 14 if(c>=2)c+=v2,v2=0; 15 mx=max(mx,c); 16 }printf("%d\n",mx);mx=c=v1=v2=0; 17 }}
T3:最大子段和
大意:有一個長度$2n-1$的序列,偶數位置是空白的你可以在裏面填上$[-K,K]$的任意數,最大化(最大子段和-最大的由正數構成的子段和)。$n \le 5000$
保證序列裏的所有數都在$[-K,K]$中。
首先可以感性理解一個結論,要麼填$-1$要麼填$K$.
然後依然可以感性理解的一個結論,最大子段和一定是$[1,2n-1],[2,2n-1],[1,2n-2],[2,2n-2]$。你可以不斷填$K$以做到這一點。
然後再次感性理解一個結論,(對於所有奇數位置)有決策單調性。
然後這題就可以做了。$dp[i][j]$表示考慮了前$i$個位置,填了$j$個$K$,此時最大的由正數構成的子段和。憑藉第二維可以得出真正的最大子段和。
然後決策單調性就可以用指針爆掃決策點(轉移形式是$max(dp[x][j-1],sum[x+1][i])$,維護兩個值相等的位置)
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n,dp[10001][2],A[10001],a[10001],K,ans; 4 int cal(int l,int r){l--;return ~l?a[r]-a[l]+(r>>1)*K-(l>>1)*K:a[r]+(r>>1)*K;} 5 int main(){ 6 freopen("subsegment.in","r",stdin);freopen("subsegment.out","w",stdout); 7 scanf("%d%d",&n,&K); n<<=1;n--; 8 for(int i=1;i<=n;i+=2)scanf("%d",&A[i]); 9 if(A[1]<0)A[1]=0; if(A[n]<0)A[n]=0; A[0]=-1; 10 for(int i=0,tot=0;i<=n;++i)a[i]=(i?a[i-1]:0)+A[i],tot=A[i]<0?0:(tot+(i&1?A[i]:K)),dp[i][0]=max(i?dp[i-1][0]:0,tot); 11 ans=max(0,cal(0,n)-dp[n][0]); 12 for(int j=1;j<=n/3;++j){ 13 int nw=j&1; 14 for(int i=1,pt=0,lst=-1;i<=n;++i)if(A[i]>=0){ 15 dp[i][j]=1e9; 16 while(pt<i&&(pt?dp[pt-1][nw^1]:0)+cal(0,pt)<=cal(0,i))pt+=2; 17 if(pt-2<i&&pt-2>lst)dp[i][nw]=min(dp[i][nw],max(pt>=3?dp[pt-3][nw^1]:0,cal(pt-1,i))); 18 if(pt<i)dp[i][nw]=min(dp[i][nw],max(pt?dp[pt-1][nw^1]:0,cal(pt+1,i))); 19 if(~lst)dp[i][nw]=min(dp[i][nw],max(dp[lst-1][nw],cal(lst+1,i))); 20 }else dp[i][nw]=1e9,lst=i,pt=i+3; 21 ans=max(ans,cal(0,n)-(K+1)*j-dp[n][nw]); 22 }printf("%d",ans+(n!=1)); 23 }