[考试反思]0607四校联考第二轮day1:恍惚
这博客咕了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 }