zoukankan      html  css  js  c++  java
  • [考试反思]0225省选模拟31:探索

    也就没啥好说的。

    今天又是把两三小时的时间献祭了给我可爱的路由器。。。

    考场上想到一半,断网,重连,诶我想到哪来着了。。?

    于是打算去干一个不太需要连贯思路的事情:打表啊!

    $T3$只有三个参数,看起来挺可做的,研究研究。

    写个暴力打了个小表找找规律,发现答案是关于$n-k$的$m$次多项式。

    于是参数减小到两个。打个更好的二维表,大概是这样的:

    1   2    3     4    5    6

    1   4    9  16  25  36

    1   6  17  36  65  106

    懒得写了。于是你可以发现$F(i,j)=F(i-1,j)+F(i,j-1)+j$然后像你们一样聪明的人就可以$AC$了

    然而我这种弱智不会用组合恒等式只拿了$70$

    然后前两道题都是暴力,$T1$想用$i-1000$枚举量剪枝,然而只有$30$分,貌似$2000$就有$50$。

    然后就没了。

    改题不顺。

    $T3$套个组合恒等式就没了于是没花多少时间。

    然后$T1$再度戳中我的痛处:斜率优化$dp$。当时一直没有学明白的专题。(现在已经在比赛的第三页了,我落了多少啊这是)

    而且我写的还是一个挺小众的方法$CDQ$于是大神们都不太愿意帮我看。

    于是搞搞这个搞搞那个调了半天并没有找到问题,于是到处找大神帮忙看看(最后看在我调了一下午的份子上总算可怜我了),然而都没有发现问题。

    结果并不是斜率优化$dp$错了,而是一个小细节。。。

    也罢,诚当花了点时间好好理解了一下斜率优化$dp$吧。结合凸包和决策单调性的知识肯定比当时空手套白狼来的好的多。。

    $T2$是个搜索。很好写啊。为什么没有先改这个。。。

    (在$OI$里现在我会的是不是只剩下打表,乱搞,搜索和模拟了?

    T1:Skip

    大意:$dp_i=dp_j - binom{i-j}{2} +a_i$。要求$i<j,a_i le a_j$。有$dp_0=-inf,a_n =inf$。求$dp_n -inf$。$n le 10^5$

    做法不少,大致列举:

    基于线段树维护单调栈。

    树状数组也可以。

    线段树维护凸包。

    $CDQ$分治值域+斜率youhua$dp$。

    也许还有别的没提,时间复杂度都是一个或两个$log$。我写的是最后一种。

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define S 100005
     4 long long dp[S];int a[S],n,o[S],q[S];
     5 bool cmp(int x,int y){return a[x]<a[y]||(a[x]==a[y]&&x<y);}
     6 long double jd(int a,int b){return 1.L*(dp[b]-dp[a]-b*(b+1ll)/2+a*(a+1ll)/2)/(a-b);}
     7 void CDQ(int l,int r){int md=l+r>>1;
     8     if(l==r)return;
     9     CDQ(l,md); sort(o+l,o+md+1); sort(o+md+1,o+r+1);
    10     int p1=l,p2=md+1,h=1,t=0;
    11     while(p1<=md||p2<=r)
    12         if(p2>r||(o[p1]<o[p2]&&p1<=md)){
    13             while(t>h&&jd(q[t-1],q[t])+1e-4>=jd(q[t],o[p1]))t--;
    14             q[++t]=o[p1];p1++;
    15         }else{
    16             while(t>h&&dp[q[h]]+a[o[p2]]-(o[p2]-q[h])*(o[p2]-q[h]-1ll)/2<=dp[q[h+1]]+a[o[p2]]-(o[p2]-q[h+1])*(o[p2]-q[h+1]-1ll)/2)h++;
    17             if(t>=h)dp[o[p2]]=max(dp[o[p2]],dp[q[h]]+a[o[p2]]-(o[p2]-q[h])*(o[p2]-q[h]-1ll)/2);p2++;
    18         }
    19     sort(o+l,o+r+1,cmp); CDQ(md+1,r);
    20 }
    21 int main(){
    22     cin>>n;n++;
    23     for(int i=1;i<n;++i)scanf("%d",&a[i]),dp[i]=-1000000000000000000;
    24     a[0]=-2000000001;a[n]=-a[0];dp[n]=-1000000000000000000;
    25     for(int i=1;i<=n;++i)o[i]=i;
    26     sort(o,o+n+1,cmp); CDQ(0,n);
    27     cout<<dp[n]-a[n]<<endl;
    28 }
    应该是最好写的一种?

    T2:String

    大意:出现了$k$种小写字母,其中出现了$1,2,...,k$次的各一种,且相邻两个字符不同。这样的字符串中字典序第$x$小的。$x le 10^{18},k le 26$

    首先,方案是指数级的,所以$k$很大时,一定是$ababababababa...bacdcdcdcdc...dcefefef...fe$这样的,只要缩小字符集填后面几位就好了。实际上是$8$。

    于是记忆化搜索,状态表示:目前前面已经出现了$x$次的字符有$k$种,则压成$sum k imes 16^{x-1}$。$16$进制压$8$个恰好$32$位,可以$int$

    哦当然还要记录上一个位置的字符已经出现了几次。然后搜就好了,排名够就扣不够就搜下去。

    代码其实好写。跑的也很快。因为这就是暴力的本质(?)。然而并不知道和题解有什么区别,可能是我太菜了吧。

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define ll long long
     4 map<int,ll>M[10];
     5 int k,apk,S=0,tim[26],rst,buc[10];char ss[888];long long c;
     6 int bit(int x){return x?1<<4*x-4:0;}
     7 ll sch(int l,int s,ll rk,int lst){
     8     if(M[lst].find(s)!=M[lst].end()&&rk>=M[lst][s])return M[lst][s];
     9     if(s==rst){
    10         if(!rk){puts(ss);exit(0);}
    11         return M[lst][s]=1;
    12     }
    13     long long pl=0,r;
    14     for(int z=S;z<26;++z)if('a'+z!=ss[l-1]){
    15         ss[l]=z+'a';tim[z]++;buc[tim[z]]++;
    16         if(buc[tim[z]]<=k-tim[z]+1)r=sch(l+1,s+bit(tim[z])-bit(tim[z]-1),rk,tim[z]),pl+=r,rk-=r;
    17         buc[tim[z]]--;tim[z]--;
    18     }return M[lst][s]=pl;
    19 }
    20 int main(){
    21     cin>>k>>c;
    22     while(k>8){
    23         for(int i=1;i<k;++i)putchar(S+'a'),putchar(S+'b');
    24         putchar(S+'a');k-=2;S+=2;
    25     }
    26     for(int i=1;i<=k;++i)rst|=bit(i);
    27     sch(0,0,c-1,9);
    28     puts("-1");
    29 }
    View Code

    T3:Permutation

    大意:求所有$n$选$k$的有序排列,按照字典序排序后,相邻两个排列的第$m$位的差的绝对值之和。$n,m,k le 10^6$

    然而时间复杂度是线性的。

    先说打表吧。第一步的思路我写在这篇博客上面的吐槽部分了。

    我最初找到的规律是$F(i,j)=F(i-1,j)+F(i,j-1)+j$。边界是$F(1,x)=x+1,F(x,0)=0$。要求的是$F(m,n-k-1)$

    于是我们就得到了第一形态的暴力。

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define mod 1000000007
     4 int qp(int b,int t,int a=1){for(;t;t>>=1,b=1ll*b*b%mod)if(t&1)a=1ll*a*b%mod;return a;}
     5 int c,n,k,a[111],m,L,ans,fac[2111111],inv[2111111],aa[11111][11111];
     6 void sch(int al,int lst){
     7     if(al==k){
     8         if(L)ans+=abs(a[m]-L);L=a[m];
     9         return;
    10     }
    11     for(int i=lst+1;i<=n;++i)a[al+1]=i,sch(al+1,i);
    12 }
    13 int main(){
    14     cin>>n>>k>>m;
    15 /*    sch(0,0);
    16     cout<<ans<<endl;*/
    17     if(n==k)return puts("0"),0;
    18     n-=k+1;/*fac[0]=1;
    19     for(int i=1;i<=2000000;++i)fac[i]=fac[i-1]*1ll*i%mod;
    20     inv[2000000]=qp(fac[2000000],mod-2);
    21     for(int i=1999999;~i;--i)inv[i]=1ll*(i+1)*inv[i+1]%mod;
    22     for(int i=0;i<m;++i)for(int j=0;j<n;++j)ans=(ans+1ll*(i==m-1?1:n-j)*fac[i+j]%mod*inv[i]%mod*inv[j])%mod,cout<<i<<' '<<j<<' '<<ans<<endl;
    23     cout<<(ans+1ll*fac[n+m]*inv[n]%mod*inv[m])%mod<<endl;*/
    24     for(int i=0;i<=n||i<=m;++i)aa[i][0]=1,aa[1][i]=i+1;
    25     for(int i=2;i<=m;++i)for(int j=1;j<=n;++j)aa[i][j]=(aa[i][j-1]+aa[i-1][j]+j)%mod;
    26     cout<<aa[m][n]<<endl;
    27 }
    View Code

    这个代码里注释掉的,一个是搜索,一个是未成型的第二形态暴力。

    考虑上面那个式子的实际含义:就是说走到当前(n,m)这个点,你要付出当前所在列编号的代价,然后可以选择向上或者向左走。直到走到(0,0)为止

    那么这个点付出代价的次数就是:从起点走到这个点的方案数。这个可以直接用一个组合数算出来。

    于是我们枚举每个点,计算其次数和贡献,就得到了总贡献。要注意特判第一行,因为$F(1,x)=x+1$并不满足上述规律。

    这样就得到了第二形态暴力。都是$O(n^2)$的可以得到$70$分。

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define mod 1000000007
     4 int qp(int b,int t,int a=1){for(;t;t>>=1,b=1ll*b*b%mod)if(t&1)a=1ll*a*b%mod;return a;}
     5 int c,n,k,a[111],m,L,ans,fac[2111111],inv[2111111],aa[5555][5555];
     6 int main(){
     7     cin>>n>>k>>m;
     8     if(n==k)return puts("0"),0;
     9     n-=k+1;fac[0]=1;
    10     for(int i=1;i<=2000000;++i)fac[i]=fac[i-1]*1ll*i%mod;
    11     inv[2000000]=qp(fac[2000000],mod-2);
    12     for(int i=1999999;~i;--i)inv[i]=1ll*(i+1)*inv[i+1]%mod;
    13     for(int i=0;i<m;++i)for(int j=0;j<=n;++j)ans=(ans+1ll*(i==m-1?1:n-j)*fac[i+j]%mod*inv[i]%mod*inv[j])%mod;
    14     cout<<ans<<endl;
    15 }
    View Code

    然后我们不考虑第一行,列出上述式子。发现对于同一列的点,它们的贡献是相同的,而它们的方案数,也就是次数,是一列组合数

    $ sumlimits_{i=a}^{b} inom{i}{y} = inom{b+1}{y+1} -inom{a}{y+1}$

    于是就可以优化到线性了。

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define mod 1000000007
     4 int qp(int b,int t,int a=1){for(;t;t>>=1,b=1ll*b*b%mod)if(t&1)a=1ll*a*b%mod;return a;}
     5 int c,n,k,a[111],m,L,ans,fac[2111111],inv[2111111],aa[5555][5555];
     6 int main(){
     7     cin>>n>>k>>m;
     8     if(n==k)return puts("0"),0;
     9     n-=k+1;fac[0]=1;
    10     for(int i=1;i<=2000000;++i)fac[i]=fac[i-1]*1ll*i%mod;
    11     inv[2000000]=qp(fac[2000000],mod-2);
    12     for(int i=1999999;~i;--i)inv[i]=1ll*(i+1)*inv[i+1]%mod;
    13     if(m>=2)for(int j=0;j<=n;++j)ans=(ans+1ll*(n-j)*fac[m-1+j]%mod*inv[m-2]%mod*inv[j+1])%mod;
    14     for(int j=0;j<=n;++j)ans=(ans+1ll*fac[m-1+j]*inv[m-1]%mod*inv[j])%mod;
    15     cout<<ans<<endl;
    16 }
    View Code

    并不知道正解在扯什么淡,但是$cbx$大神有非常妙的理解方法。

    我们考虑题目含义,相邻两个排列,对于第$k$位发生了变化,此时$k$位以后一定是由$x_k,...,n-3,n-2,n-1$变为了$x_k+1,x_k+2,x_k+3,...$这样的话后面这个玩意是确定了。

    方案数唯一,某一位的变化也可以快速计算。

    而前面只要乱选就好了,是个组合数的样子。于是我们只要枚举第$x(x le k)$位当前是$y$的贡献,$O(n^2)计算就好了。

    也可以用组合恒等式优化到$O(n)$。最终式子代码写出来差不多和我的也是是一样的。

    但是显然这个思路更大神。我太菜了。

  • 相关阅读:
    形象的理解Strong和Weak
    iOS开发中常见的一些异常
    离屏渲染
    如何从海量IP中提取访问最多的10个IP
    XJOI3363 树3/Codeforces 682C Alyona and the Tree(dfs)
    XJOI 3578 排列交换/AtCoder beginner contest 097D equal (并查集)
    XJOI 3605 考完吃糖(DAG图dfs)
    POJ 3660 Cow Contest(传递闭包)
    XJOI 3601 技能(贪心+二分)
    51nod 1421 最大MOD值(高妙的调和级数复杂度)
  • 原文地址:https://www.cnblogs.com/hzoi-DeepinC/p/12364201.html
Copyright © 2011-2022 走看看