[考试反思]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} imes 10^6 < MAX long long$

所以只要拿$10^{12}$做进制数写个类似高精的东西,每次乘除都不会爆掉$long long$。还是挺好写的。详见代码。

然后回到这道题上来,我们不难发现执行次数与$p_i$无关而只与$e_i$有关。这是指数上的东西,我们大胆猜测不会很多。

集合大小最大也就$18$。所有无序状态数也不到$2 imes 10^5$。

不难发现所有集合都可以用$2^{e_1} imes 3^{e_2} imes 5^{e_3} imes 7^{e_4} imes ...(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 imes 10^5 imes 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
",dp[M[hsh]]);
47     }
48 }
View Code

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
",mx);mx=c=v1=v2=0;
17 }}
View Code

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 }
View Code
原文地址:https://www.cnblogs.com/hzoi-DeepinC/p/13139585.html