[题解]CF Round #384 (Div.2)

743A:Vladik and flights

题意简述

n 个机场,编号为[1,n]这些机场分属两个公司,编号为 0 1
在同家公司的机场间行动不耗代价,在不同公司的机场间移动,从 i j话费 |ij| 的代价。
问从 x y需要花费多少代价。

数据范围

1n105
ai[0,1]

思路

水。
x,y 在同家公司答案为 0 ,不同公司答案为1

代码

#include<cstdio>
using namespace std;
int n,a,b;
char st[100010];
int main()
{
    scanf("%d%d%d",&n,&a,&b);
    scanf("%s",st+1);
    if (st[a]==st[b])
        printf("0");
    else
        printf("1");
    return 0;
}

743B:Chloe and the sequence

题意简述

数列 a 初始只有一个元素1,进行 n1 次操作。
i 次操作将a,变为 a,i+1,a ,即将 a 复制一次再将i+1插入他们中间。
求第 k 个元素是多少。

数据范围

1n50
1k2n1

思路

经过观察可以发现。
答案即为 k 的二进制表示中最小的1的位置 +1

代码

include<cstdio>
using namespace std;
int n;
long long k;
int main()
{
    scanf("%d%I64d",&n,&k);
    for (int i=0;i<=n;i++)
        if (k&(1LL<<i))
        {
            printf("%d",i+1);
            break;
        }
    return 0;
}

743C:Vladik and fractions

题意简述

给出 n ,问2n能否表示为 1x+1x+1z 的形式,其中 x,y,z 互不相等且 x,y,z109

数据范围

1n104

思路

这个题比较有意思…
首先 2n 可以拆成 1n+1n
然后瞬间联想到裂项项消……
1n1n+1=1n(n+1)
于是答案就是 n,n+1,n(n+1)
一看 n 的范围,简直精致。

代码

#include<cstdio>
using namespace std;
int n;
int main()
{
    scanf("%d",&n);
    if (n==1)
        printf("-1");
    else
        printf("%d %d %d\n",n,n+1,n*(n+1)); 
    return 0;
}

743D:Chloe and pleasant prizes

题意简述

给出一棵n个节点的带权树。
求两个不相交的子树,权值和的最大值。
如果没有这样的方案,输出 1

数据范围

2n105
109ai109

思路

树形DP。
DP出 mxi 数组,表示以 i 为根的子树中,最大的子树权值和。
以一个节点为根的答案一定是它所有孩子的mx的最大值和次大值之和。
所有的答案扫一遍就行了。
注意一条链的情况无解。

代码

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
#define INF 1LL<<60 
struct edge{
    int s,t,next;
}e[400010];
int head[200010],cnt;
void addedge(int s,int t)
{
    e[cnt].s=s;e[cnt].t=t;e[cnt].next=head[s];head[s]=cnt++;
    e[cnt].s=t;e[cnt].t=s;e[cnt].next=head[t];head[t]=cnt++;
}
int n,u,v;
long long sum[200010],mx[200010];
int size[200010],val[200010];
long long ans=-INF,se;
void dfs(int node,int lastfa)
{
    sum[node]=val[node];
    size[node]=0;
    for (int i=head[node];i!=-1;i=e[i].next)
        if (e[i].t!=lastfa)
        {
            dfs(e[i].t,node);
            sum[node]+=sum[e[i].t];
            size[node]++;
        }
    mx[node]=-INF;
    se=-INF;
    for (int i=head[node];i!=-1;i=e[i].next)
        if (e[i].t!=lastfa)
        {
            if (mx[e[i].t]>mx[node])
            {
                se=mx[node];
                mx[node]=mx[e[i].t];
            }
            else if (mx[e[i].t]>se)
                se=mx[e[i].t];
        }
    if (size[node]>1)
        ans=max(ans,mx[node]+se);
    mx[node]=max(mx[node],sum[node]);
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
        scanf("%d",&val[i]); 
    memset(head,0xff,sizeof(head));
    cnt=0;
    for (int i=1;i<n;i++)
    {
        scanf("%d%d",&u,&v);
        addedge(u,v);
    }
    dfs(1,1);
    if (ans==-INF)
        printf("Impossible");
    else
        printf("%I64d",ans);
    return 0;
}

743E:Vladik and cards

题意简述

给出 n 个元素的一个序列,序列中所有元素[1,8]
问最长的满足如下条件的子序列长度:
1.子序列中所有相同元素相邻。
2.子序列中任意两种元素的个数之差的绝对值 1

数据范围

1n1000
ai[1,8]

思路

二分+DP。
我们可以发现,所有元素的出现次数之可能为 k k+1
k 对于答案的影响是单调的。
所有我们可以二分这个k,通过DP验证答案。
如何DP?
首先预处理。
pos[i][j] 表示 i 元素第j次出现的位置。
ord[i] 表示原序列的第 i 个的那种元素是第几次出现。
DP数组f[i][mask]表示进行到第 i 个数,已经用过的数为mask的最长长度。
f[i][mask] 只可能有三种状态转移:
1. i 不取。
2.取i再加上它之前的同种元素共 k 个。
3.取i再加上它之前的同种元素共 k+1 个。
据此转移就可以了。
时间复杂度 O(28nlog(n8))
代码

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int f[1010][260];
int n,lim,l,r,mid,tmp,ans,now;
int seq[1010],ord[1010],cnt[10],pos[10][1010];
bool judge(int x)
{
    tmp=0;
    memset(f,0xef,sizeof(f));
    f[0][0]=0;
    for (int i=1;i<=n;i++)
        for (int j=0;j<=lim;j++)
        {
            f[i][j]=f[i-1][j];
            if (j&(1<<seq[i]))
            {
                now=ord[i];
                if (x>0&&now-x+1>=1)
                    f[i][j]=max(f[i][j],f[pos[seq[i]][now-x+1]-1][j^(1<<seq[i])]+x);
                if (now-x>=1)
                    f[i][j]=max(f[i][j],f[pos[seq[i]][now-x]-1][j^(1<<seq[i])]+x+1);
            }
        }
    if (x==0)
        for (int i=0;i<=lim;i++)
            tmp=max(tmp,f[n][i]);
    else
        tmp=f[n][lim];
    return tmp>0;
}
int main()
{
    scanf("%d",&n);
    lim=(1<<8)-1;
    for (int i=1;i<=n;i++)
    {
        scanf("%d",&seq[i]);
        seq[i]--;
    }
    for (int i=1;i<=n;i++)
    {
        ord[i]=++cnt[seq[i]];
        pos[seq[i]][cnt[seq[i]]]=i;
    }
    l=0,r=n/8;
    while (l<=r)
    {
        mid=(l+r)>>1;
        if (judge(mid)) 
            ans=tmp,l=mid+1;
        else
            r=mid-1;
    }
    printf("%d",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值