codeforces 121 (比赛) codeforces 191C

 

哎,CF终于变色了

A题:我写了个二分,囧。。。

B题:模拟题,水

C题:想了半天,水王(一学长)告诉我是DP,果断敲了

dp[i][j]表示i字符走到j字符最大的权值

View Code
int dp[30][30];
int max(int a,int b){
    return a>b?a:b;
}
int main()
{
    int n,i,j,k;
    char  s[15];
    while(scanf("%d",&n)!=EOF)
    {
        memset(dp,0,sizeof(dp));
        for(k=1;k<=n;k++)
        {
            scanf("%s",s);
            int len=strlen(s);
            for(i=0;i<26;i++)
            {
                if(dp[i][s[0]-'a'])
                   dp[i][s[len-1]-'a']=max(dp[i][s[len-1]-'a'],dp[i][s[0]-'a']+len);
            }
            if(len>dp[s[0]-'a'][s[len-1]-'a'])
                dp[s[0]-'a'][s[len-1]-'a']=len;
        }
        int ans=0;
        for(i=0;i<26;i++)
        {
            if(dp[i][i]>ans)
                ans=dp[i][i];
        }
        printf("%d\n",ans);
    }
    return  0;
}

 

D:

从右往左维护一个堆或者一个multiset就好了

E:

题意:给你一颗树,再给你一些点对,数据范围都是10W,对于每一个点对,这两个点之间的简单路径上的点都会被经过一次

最后输出每条边被经过的次数

这题最神奇了,赛后搞了很长时间,死磕着大婶们的各种代码,终于在一个夜黑风高的晚上被我搞出来了,特此mark一下我的死磕精神,hehe

先搜一遍树,得出时间戳以及各个点的深度等的信息,目的是为了找点对的LCA(最近公共祖先),以及把树变成有向的

下面就是核心的地方了

记录一个数组c[];

对于一对点 (u v)   他们的LCA(u,v)=X

c[u]++;c[v]++;c[x]+=2;也就是说当某个点是点对中的一个点是,就在标记数组中加个1,如果是某两个点的最近公共祖先,就加个2

开始一直不理解这样子要干嘛,无奈之下我只能搞了组数据,单步调试,调了许久,总算是搞清楚这个标记数组的神奇之处了

好了不废话了,看图

 

如图所示的一棵树(已经调整了方向,以1为根),现在假如有两对点(12  , 14) (14 , 15)则

c[12]=1,c[14]=2,c[15]=1,la[7]=2,la[3]=2;

然后我们开始第二次dfs,判断每条边被经过了几次 主要是在回溯过程中完成的。

每次在回溯时都会把子树节点相应的信息传递上来,比如从v回溯到u,我们就要先判断有几个v的子节点会 “冲出”  v 经过u->v之间的边

当然这个数量肯定不是子树中被标记的数量,因为可能某两个点的LCA也在子树内,这两个点就不会上来了,即不会经过u-v之间的边了

所以一个LCA可以阻止子树内的两个点(即输入的某对点对)走出这棵子树(这就解释了la[x]+=2这一步),在程序中就表示为: 回溯到某个点时,先减去这个点的la[u]的值,表示又少了la[u]数量的点走上来,然后从v回溯到u的过程就可以得出u->v之间的边被经过的次数了,回溯完了,所有的答就案都出来了

以上图为例:图中红色的左边上的数字是这几条边的编号(为了方便图中只用到这几条边)

先遍历到12,开始往6回溯,edge[1]被经过了一次,edge[1]=1;13 和 6 之间的边没有被 经过,所以回溯到6的时候

总共有sum=1的点走上来了,继续往3回溯,先减去6的la[]值(为0),所以,6与3之间的边被经过了几次也可以得出来了

edge[4]=sum+c[6]  c[6]为0,所以edge[4]=1;

右边的回溯情况:

14、15回溯到7,edge[2]加了2,edge[3]加了1,sum=3再从7回溯到3的过程中发现7是lca,所以先减去la[7]的值

此时   sum=1,相当于7的子树内有一个点会走出来,经过edge[5],这样子edge[5]=1;继续这样回溯回去就好了。。。。。。

这样复杂度就为求LCA的复杂度了,2*n*log(2*n)    记录的时间戳序列为2*n的长度

我的代码

View Code
#include<string.h>
#include<stdio.h>
#include<vector>
#include<math.h>
using namespace std;
const int M =100100;
const double inf = 1e20;
int min(int a,int b){return a<b?a:b;}
int n,k,tdfn,tot;
int dp[20][2*M],vis[M];
int B[2*M],LOG[2*M],used[M],F[2*M],pos[M],c[M],la[M];
vector<int> edge[M],eg[M];
int ans[M];
void rmq_init(int n,int num[])
{
    int i,j;
    for(j=1;j<=n;j++) 
        dp[0][j]=num[j];
    for(j=1;j<=LOG[n];j++)
    {
        int limit=n+1-(1<<j);
        for(i=1;i<=limit;i++)
        {
            int x=i+(1<<j>>1);
            dp[j][i]=min(dp[j-1][x],dp[j-1][i]);
        }
    }
}
int rmq(int l,int r,int num[])
{
    int m=LOG[r-l+1];
    return min(dp[m][l],dp[m][r-(1<<m)+1]);
}
void dfs(int s)
{
    int i,t;
    used[s]=1;
    int tmp=++tdfn;
    B[++tot]=tmp;F[tmp]=s;
    pos[s]=tot;
    int sz=edge[s].size();
    for(i=0;i<sz;i++)
    {
        t=edge[s][i];
        if(used[t])  continue;
        dfs(t);
        B[++tot]=tmp;//backtrack
    }
    used[s]=0;
}
int lca(int a,int b)
{
    if(pos[a]>pos[b]) swap(a,b);
    int ans=rmq(pos[a],pos[b],B);
    return F[ans];
}
void init()
{
    tdfn=0; tot=0;
    memset(used,0,sizeof(used));
    dfs(1);
    rmq_init(tot,B);
}
int solve(int u,int edge_num)//核心
{
    int sum=0;
    used[u]=1;
    int sz=edge[u].size();
    for(int i=0;i<sz;i++)
    {
        int to=edge[u][i];
        if(used[to]) continue;
        sum+=solve(to,eg[u][i]);
    }
    sum-=la[u];
    if(edge_num) ans[edge_num]=sum+c[u];
    return ans[edge_num];
}
int main()
{
    int i,n,m,a,b,w;
    LOG[0]=-1;
    for(i=1;i<2*M;i++)  LOG[i]=LOG[i>>1]+1;
    while(scanf("%d",&n)!=EOF)
    {
        for(i=0;i<=n;i++){ edge[i].clear();c[i]=0;}
        char str[5];
        for(i=1;i<n;i++)
        {
            scanf("%d%d",&a,&b);
            edge[a].push_back(b);
            edge[b].push_back(a);
            eg[a].push_back(i);
            eg[b].push_back(i);
        }
        init();
        scanf("%d",&k);
        while(k--)
        {
            scanf("%d%d",&a,&b);
            c[a]++;
            c[b]++;
            int x=lca(a,b);
            la[x]+=2;
        }
        solve(1,0);
        for(i=1;i<n;i++) printf("%d ",ans[i]);
        puts("");
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值