20190822考试

期望:80 + 30 + 10 = 120.实际: 50 + 30 + 10 = 90.

T1:小P写出二叉树的中序遍历,但他没判断左右儿子是否存在就遍历,我们设访问到虚点时输出'#',给定数字(存在点)与'#'(虚点),要求你判断是否存在这样的二叉树?

S1:蒟蒻理解题意错了,以为只要是给定的条件能组成二叉树即可(条件给多少个虚点就访问多少个虚点),立马想到了区间DP,类似于加分二叉树的做法.设f[ i ][ j ]表示区间[ i , j ]区间能否组成二叉树,f[ i ][ j ]唯一为true的情况为f[ i ][ k -1 ] = true,f[ k + 1 ][ j ] = true,s[ k ][ 0 ] ! =' # ',同时注意枚举的根为i/j的情况,还想了一个剪枝,就是我们判断f[ i ][ j ]为true后,只要找到中间点 k = i-1,对称区间 j1 = i -2,i1 = j1 -len + 1.只要f[ i1 ][ j1 ]&&f[ i ][ j ]&&s[ k ][ 0 ] ! = '#',我们就就能判断f[ i1 ][ j ]区间合法.这样我们就能O(n^3)过1000的点(80分),但测出来前3个点WA了,与dalao交流后发现自己理解错了,可能存在虚点就一定要访问,并不是给定多少个就访问多少个就可以了.那么唯一合法形式就为"# 数字 # 数字 # 数字 # ......",直接判断即可,注意'#'数量小于数字数量+1的情况.

 

#include<iostream>
#include<cstdio>
using namespace std;
#define re register
int T,n;
string s[100010];
int main()
{
    freopen("traversal.in","r",stdin);
    freopen("traversal.out","w",stdout);
    scanf("%d",&T);
    while(T--){
        int flag=0,ans=1,cnt=0,sur=0;
        scanf("%d",&n);
        for(re int i=1;i<=n;++i){
            cin>>s[i];
            if(s[i][0]=='#')
                ++cnt;
            else ++sur;
        }
        if(cnt!=sur+1){
            printf("No\n");
            continue;
        }
        for(re int i=1;i<=n;++i){
            if(i==1){
                if(s[i][0]=='#')
                    flag=1;
                else{
                    ans=0;
                    break;
                }
            }
            else{
                if((flag&&s[i][0]=='#')||(!flag&&s[i][0]!='#')){
                    ans=0;
                    break;
                }
                if(flag)
                    flag=0;
                else flag=1;
            }
        }
        if(ans)
            printf("Yes\n");
        else printf("No\n");
    }
    return 0;
}

 

T2:小 P 最近迷上了一款选数游戏.游戏一开始会给定 b 组数字序列,每一组序列都包含相同的n 个元素,每个元素都在 1 到 9 以内.小 P 必须从每组数字序列中都挑选一个数字,按顺序组合成一个大整数.例如如果小 P 挑选 1 和 2,那么他就得到了整数 12.之后,小 P 需要计算这个大整数除以 x 的余数。如果这个余数正好等于 k,那么小 P 就取得了游戏的胜利.现在小 P 想知道,一共有多少种胜利的方式.两种方式被视为不同,当且仅当至少在一组序列中,小 P 挑选了不同位置上的数字.由于答案可能很大,你只要对1000000007取模后的值即可.

S2:考试中只写了30分的爆搜.对于60分,我们考虑dp.先预处理一个cnt[ i ][ j ]表示从%x余数为i变化到%x余数为j的方案数,我们枚举余数[ 0 , x -1 ],在枚举[ 1 , n ]区间的val[ k ] ,那么转换的余数 j = ( i * 10 + val[ k ] ) %x , cnt[ i ][ j ]+1.为什么i要乘10?看一个例子,1546 % x = ( ( ( ( 1 %x ) * 10  + 5) % x) *10 + 4) %x)*10 + 6 )%x,所以我们就前一位数的余数变为加一位数后的余数要乘10再取摸.我们设f[ k ][ i ]表示第 k次选择,得到余数为i的次数.有转移:f[ k ][  i  ] + = f[ k -1 ][ j ]*cnt[ j ][ i ].(k为第几次选,i,j为余数).初始化f[ 0 ][ 0 ] = 1,蒟蒻想这个初始化为什么有意义?因为开始蒟蒻是预处理 k =1 的f[ 1 ][ i ]的情况,从k = 2 开始转移的.也就是让f[ 1 ][ i ]从f[ 0 ][ 0 ]的到转移,f[ 1 ][ i ] + = f[ 0 ][ 0 ]*cnt[ 0 ][ i ],发现此时的cnt[ 0 ][ i ]为i = ( 0*10+val[ k ] )%x = ( val[ k ] ) %x.就为f[ 1 ][ i ]的预处理状态.60分code:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define re register
const int maxn=500010;
const int mod=1000000007;
int n,b,k,x,val[maxn],cnt[101][101],f[10001][101];
int main()
{    
    freopen("number.in","r",stdin);
    freopen("number.out","w",stdout);
    scanf("%d%d%d%d",&n,&b,&k,&x);
    for(re int i=1;i<=n;++i)
        scanf("%d",&val[i]);
    for(re int i=0;i<=x-1;++i)
        for(re int j=1;j<=n;++j){
            int p=i,q=(p*10+val[j])%x;
            ++cnt[p][q];
        }
    f[0][0]=1;
    for(re int g=1;g<=b;++g)
        for(re int i=0;i<=x-1;++i)
            for(re int j=0;j<=x-1;++j)
                f[g][i]=(f[g][i]%mod+1ll*f[g-1][j]*cnt[j][i]%mod)%mod;
    printf("%d",f[b][k]);
    return 0;
}

但b高达1e9,这样显然超时.我们发现f[ g ][ i ]由f[ g -1 ][  j ]转移,g状态由g - 1的转态,我们考虑将转移过程放入矩阵转移中,设初始矩阵f[ 0 ][ 0...x-1 ](注意只有f[ 0 ][ 0]有意义),转化矩阵为cnt[ i ][  j ],我们对cnt[ i ][ j ]进行b次矩乘,矩乘如何替代dp的转移?矩乘是行与列相乘求和,例如x = 4时,f[ 1 ][ 3 ]为Σf[ 1 ][ 1 ]*f[ 1 ][ 3 ] + f[ 1 ][ 2 ]*f[ 2 ][ 3]+f[ 1 ][ 3 ]*f[ 3 ][ 3 ],显然矩乘性质替代了dp的第二层的枚举.最后输出f[ 0 ][ k ]就是初始矩阵与转移矩阵在矩乘,只有f[ 0 ][ 0 ]等于1,那么ans就为f[ 0 ][ k ].

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define re register
#define LL long long
const int maxn=500010;
const int mod=1000000007;
long long n,b,k,x,ans,val[maxn];
struct chang{
    LL cnt[101][101];
    chang(){memset(cnt,0,sizeof(cnt));}
    chang operator*(const chang p){
        chang ans;
        for(re LL g=0;g<x;++g)
            for(re LL i=0;i<x;++i)
                for(re LL j=0;j<x;++j){
                    ans.cnt[i][j]=(ans.cnt[i][j]%mod+(1ll*cnt[i][g]*p.cnt[g][j])%mod)%mod;
        }
        return ans;
    }
};
chang qsm(chang a,int b)
{
    chang ans;
    for(re LL i=0;i<x;++i)
        ans.cnt[i][i]=1;
    while(b){
        if(b&1) 
            ans=ans*a;
        a=a*a;
        b>>=1;
    }
    return ans;
}
int main()
{
    freopen("number.in","r",stdin);
    freopen("number.out","w",stdout);
    scanf("%lld%lld%lld%lld",&n,&b,&k,&x);
    for(re LL i=1;i<=n;++i)
        scanf("%lld",&val[i]);
    chang a;
    for(re LL i=0;i<x;++i)
        for(re LL g=1;g<=n;++g){
            LL j=(10*i+val[g])%x;
            ++a.cnt[i][j];
        }
    a=qsm(a,b);
    printf("%lld",a.cnt[0][k]);
    return 0;
}

T3:2075 年,太阳即将毁灭,地球已经不适合人类生存,而面对绝境,人类将开启“流浪地球”计划,试图带着地球一起逃离太阳系,寻找人类新家园。为了获得对推动地球前进的动力,人们发明了一种超高能粒子发射装置。发射装置一共有 n 个节点,节点编号为 1–n,由 n − 1 条等长的粒子加速通道联通,加速通道是双向的,且任意两个节点之间可以互相到达。每个加速通道的长度都是 1。但不同加速通道能加速的粒子质量是不同的,其中第 i 条加速通道只能加速质量小于等于 a i 的粒子,其余粒子可以通过,但无法获得加速效果。粒子连续通过不同单位长度的加速可以获得不同的能量,甚至可能获得负能量。粒子最终的总能量是多次连续加速获得的能量之和。科学家们为了测试发射装置的性能,准备了 q 次试验,一次试验可以被如下描述:u, v, l:从 u 点发射一个质量为 l 的粒子,该粒子最终从 v 点射出。粒子的初始能量为 0,且粒子只走最短路。现在,你作为本次实验的执行总管,你需要提交每次实验粒子的最终总能量。由于装置耗能巨大,你需要在有限时间内完成实验.

①:对于①②点,我们选择求最短路,同时用前驱数组记录路径,按照定义求ans.复杂度:O((n+n+1)log(n+1))*q.

②:对于第③点,我们直接判断l,如果l>质量限制,ans=0.如果l<=质量限制,直接用dis[ s ]+dis[ t ]-s*dis[ lca(s,t)]求出树上两点距离,在找出距离对应的ans.O(nlogn+qlogn).

③:对于第④点,考虑求出两点的lca,将路劲划分为s--->lca,t--->lca.每次只走father,这样我们用O(路径长)求出路劲长,再求ans,期望复杂度可以卡过去,考虑s,t就为lca情况.

④:对于第⑤⑥点,对于粒子质量为0直接求链长再求ans.对于粒子质量为1,考虑用线段树维护一个区间1的个数,再考虑预处理每一个有连续1的区间[ L,R ],同时对于每个区间id  处理出sum[ i ]表示前i个区间的长度,显然[ L,R ]单调递增,考虑对于查询的区间[L,R],我们二分查找第一个大于等于L的id1与第一个小于等于R的id2,这样我们得到sum[id2]-sum[id1-1]为这段区间连续1的个数,再用线段树求出的1的总数-连续1的个数=不连续1的个数,最后剩下的就为0的个数.根据定义求出ans即可.

⑤:对于第10.11点,考虑将③与④的结合,用③求出树上路径,用④方法求出大于l的连续与不连续个数,应为随机数据,期望复杂度能过.

这样我们愉快地拿下40pts,暴力选手告辞.


 

posted @ 2019-08-23 12:13  xqyxqy  阅读(122)  评论(0编辑  收藏  举报