20171009离线赛总结

考试时的思路:


第一题
直接枚举
正着循环,倒着循环,求出每个点对应的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 100086
const 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分多少水过来了,都算是好事。不过第二题的递推没想到还是可惜了。接下来要看到这种题目要多往递推的方面想,不要整体考虑,而是要分成小问题来逐个分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值