20171009离线赛总结

来源:互联网 发布:nx飞行战记 知乎 编辑:程序博客网 时间:2024/06/01 10:10

考试时的思路:


第一题
直接枚举
正着循环,倒着循环,求出每个点对应的L和R
第二题
20:32 2017/10/9
看了半天,把所有可能的区间预处理出来,dfs。
第三题
30分的话,用二进制枚举,看一条边取还是不取。
可以先把链的写了,输入的u到v的路径就变成了一个区间,这样的话,问题就简化为区间调度问题。
按照终点排序,然后贪心。

题解:

第一题:双击

  这道题还是相当水的,只要正着扫一遍,倒着扫一遍,每次只要判断一下是否属性发生了改变就可以了,只不过我对拍了两个多小时,没有拍出来,很难受。

#include<iostream>#include<cctype>#define M 500086#define FOR(i,a,b) for(register int i=(a),i##_end_=(b);i<=i##_end_;++i)#define DOR(i,a,b) for(register int i=(a),i##_end_=(b);i>=i##_end_;--i)using namespace std;char A[M];int n,m;int L[M],R[M];bool check(char c){return isdigit(c);}int main(){    cin>>n>>m;    scanf("%s",A);    int x;    bool f=check(A[0]);    int pos=0;    FOR(i,0,n-1){        if(f==check(A[i]))L[i]=pos;        else {            f=check(A[i]);            pos=i;            L[i]=i;        }    }    f=check(A[n-1]);    pos=n-1;    DOR(i,n-1,0){        if(f==check(A[i]))R[i]=pos;        else {            f=check(A[i]);            pos=i;            R[i]=i;        }    }    while(m--){        scanf("%d",&x);        printf("%d %d\n",L[x],R[x]);    }    return 0;}

第二题:取子串


  太宗对这题的处理非常好,大家可以看一下他的题解。
  这道题考察的是递推,但是我一开始想的是怎么从区间来考虑,但是由于各个区间之间有交叉,相离等情况,想着想着脑子又炸掉了。其实这道题的递推还是比较容易的(至少小C是这么认为的),我们用三个数组来存储,一个是dp,用于存储取用i的方案,一个是DP,用于存储不论是否取用i的方案,以及sum,用于存储前缀和。
  我们把问题可以这么看,如果第i个点满足条件,即可以与前面的形成一个T区间,那么就可以吧前i-m+1个点与这个区间合并成一个新的区间方案,同时把之前i-m的方案和加上来,因为每个方案都可以与这个新的区间合并。所以dp[i]=sum[i-m]+i-m+1。如果i不能满足条件,那么就可以把之前的方案加过来,相当于在每个方案后面接上了一个i。即:dp[i]=dp[i-1]。
  下面就是如何快速判断是否满足形成一个T区间了。
  这道题用的是哈希算法,以一个质数为基数(这里我们选择233,为什么呢?因为它是质数(玄学))然后我们就可以用unsigned类型的单位进行存储了(unsigned long long 或unsigned int)因为unsigned 类型可以自然溢出,不需考虑是否为质数(至于为什么不会重复,玄学)。预处理出T的哈希值,HashT=HashT*Hex+T[i] (Hex=233)。然后预处理出每一位S的哈希值。 Ha[i]=Ha[i-1]*Hex+A[i]。最后求出每一位的基数Base[i]=Base[i-1]*Hex。只需判断(Ha[i]-Ha[i-m]*Base[m]==HashT)是否成立就可以了
  不过这道题的水分,就我写出来了,方法也是比较简单的,预处理出每个满足条件的区间,然后在dfs时存储选取的区间的末尾,再接下来选取,如果下一个区间的左端点大于dfs的末尾,就可以接上去,多一种方案。

30分水分代码:

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#define M 100086#define P 1000000007#define FOR(i,a,b) for(int i=(a),i##_end_=(b);i<=i##_end_;++i)#define DOR(i,a,b) for(int i=(a),i##_end_=(b);i>=i##_end_;--i)using namespace std;bool same[1050][1050];struct node {    int L,R;} block[M];char A[M],B[M];int n,m,t=0;int top;long long ans=0;bool mark[M];void dfs(int ed){    FOR(i,1,top)if(!mark[i]){        if(block[i].L>ed){            mark[i]=true;            ans++;            ans%=P;            dfs(block[i].R);            mark[i]=false;        }    }}void solve1() {    FOR(i,1,n)if(A[i]==B[1])FOR(j,1,i)FOR(k,i,n)same[j][k]=true;    top=0;    FOR(i,1,n)    FOR(j,i,n)if(same[i][j]){        top++;        block[top].L=i;        block[top].R=j;    }    dfs(0);    cout<<ans<<endl;}int main() {    scanf("%s %s",A+1,B+1);    n=strlen(A+1);    m=strlen(B+1);    solve1();    exit(0);    return 0;}

AC代码:

#include<cstdio>#include<cstring>#define Add(A,B) A=(A+(B)%P)%P//非主流写法,请勿尝试!!!#define M 100086const int P=1e9+7,Hex=233;char A[M],B[M];int dp[M];int sum[M],DP[M];int n,m;unsigned int Ha[M],Base[M],T;int main() {    scanf("%s%s",A+1,B+1);    n=strlen(A+1);    m=strlen(B+1);    for(int i=1;i<=m;++i)T=T*Hex+B[i];    Base[0]=1;    for(int i=1;i<=m;++i)Base[i]=Base[i-1]*Hex;    for(int i=1;i<=n;++i)Ha[i]=Ha[i-1]*Hex+A[i];    for(int i=m;i<=n;++i){        if(Ha[i]-Ha[i-m]*Base[m]==T)Add(dp[i],sum[i-m]+i-m+1);        else dp[i]=dp[i-1];        Add(DP[i],DP[i-1]+dp[i]);        Add(sum[i],sum[i-1]+DP[i]);    }    printf("%d\n",DP[n]);    return 0;}

第三题:Paths

  这道题有一个比较重要的性质:如果树上两条路径相交,那么必定有:LCA较深的路径的LCA会在LCA较浅的路径上,因此我们还可以得到一个性质:如果要选取两条路径,一定会选取LCA较深的路径,让改点对其他点的影响达到最小。(有点类似于区间调度问题)。
  因此我们可以按照路径LCA深度大小进行排序,如果已被标记过,就不取用,否则就把该区间的子树全都标记掉。若已被标记过就continue 掉。
  这道题的水分,二进制枚举也是比较好写的,由于代码长的让人恶心,就不介绍了。
  代码:
  

#include<cstdio>#include<algorithm>#include<vector>#define M 100050#define FOR(i,a,b) for(register int i=(a),i##_end_=(b);i<=i##_end_;++i)#define DOR(i,a,b) for(register int i=(a),i##_end_=(b);i>=i##_end_;--i)using namespace std;int Fa[20][M],dep[M];int n,m;bool mark[M];vector<int>edge[M];void dfs(int x,int f) {    Fa[0][x]=f;    dep[x]=dep[f]+1;    FOR(i,0,edge[x].size()-1) {        int y=edge[x][i];        if(y==f)continue;        dfs(y,x);    }}void Init() {FOR(j,1,17)FOR(i,1,n)Fa[j][i]=Fa[j-1][Fa[j-1][i]];}void Up(int &b,int step) {FOR(i,0,17)if(step&(1<<i))b=Fa[i][b];}int LCA(int a,int b) {    if(dep[a]>dep[b])swap(a,b);    Up(b,dep[b]-dep[a]);    if(a==b)return a;    DOR(j,17,0)if(Fa[j][a]!=Fa[j][b])a=Fa[j][a],b=Fa[j][b];    return Fa[0][a];}struct node {    int fr,to,lca;    void Init() {lca=LCA(fr,to);}    bool operator <(const node &A)const {return dep[lca]>dep[A.lca];}} A[M];void Mark(int x,int f) {    mark[x]=true;    FOR(i,0,edge[x].size()-1){        int y=edge[x][i];        if(y==f)continue;        if(mark[y])continue;        Mark(y,x);    }}int main() {    scanf("%d%d",&n,&m);    FOR(i,1,n-1) {        int a,b;        scanf("%d%d",&a,&b);        edge[a].push_back(b);        edge[b].push_back(a);    }    dfs(1,0);    Init();    FOR(i,1,m) {        scanf("%d%d",&A[i].fr,&A[i].to);        A[i].Init();    }    sort(A+1,A+m+1);    int ans=0;    FOR(i,1,m) {        if(mark[A[i].fr]||mark[A[i].to])continue;        Mark(A[i].lca,Fa[0][A[i].lca]);        ++ans;    }    printf("%d\n",ans);    return 0;}

总结:

  这次考得不是很好,但是,该水的分都拿来了,第一题虽说对拍半天没出结果,但是60分多少水过来了,都算是好事。不过第二题的递推没想到还是可惜了。接下来要看到这种题目要多往递推的方面想,不要整体考虑,而是要分成小问题来逐个分析。

原创粉丝点击