CF#403(Div.2) 解题报告

245 篇文章 0 订阅
72 篇文章 0 订阅

A

题意简述

有2n双袜子,编号为1..n。
按顺序从包中拿出,如果这只袜子的另一只还没有拿出,就放在桌子上,否则将桌子上的另一只拿走。
求桌子上最多有多少只袜子。

数据范围

1n105

题解

模拟。。

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<queue>
#include<ctime>
#include<cmath>
#include<map>
using namespace std;

int n,x,now,ans;
int cnt[100005];

int main()
{
    scanf("%d",&n);
    for (int i=1;i<=2*n;++i)
    {
        scanf("%d",&x);
        if (!cnt[x])
        {
            ++now;
            ++cnt[x];
        }
        else --now;
        ans=max(ans,now);
    }
    printf("%d\n",ans);
}

B

题意简述

有n个人,站在一个数轴上,位置分别为x1..xn。
每个人有一个速度v1..vn,他可以向左走或者向右走。
求所有的人走到同一个点的最短时间(注意这个点不一定是整点)。
special judge 相对误差不超过 106

数据范围

2n60000

题解

实数二分时间mid
每一个人在mid的时间里能走到的范围是一段区间
判断n个区间是否有交集即可

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<queue>
#include<ctime>
#include<cmath>
#include<map>
using namespace std;

const double eps=1e-6;
int n;
double x[60005],v[60005],ans;

bool check(double mid)
{
    double Min=1e10,Max=-1e10;
    for (int i=1;i<=n;++i)
    {
        double l=x[i]-v[i]*mid,r=x[i]+v[i]*mid;
        Min=min(Min,r);Max=max(Max,l);
    }
    return Min-Max>eps;
}
double find()
{
    double l=0.0,r=1e9,mid,ans=0;
    while (r-l>eps)
    {
        mid=(l+r)/2.0;
        if (check(mid)) ans=r=mid;
        else l=mid;
    }
    return ans;
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;++i) scanf("%lf",&x[i]);
    for (int i=1;i<=n;++i) scanf("%lf",&v[i]);
    ans=find();
    printf("%.12lf\n",ans);
}

C

题意简述

一棵树,将其染色,任意有两条边相连的3元组3个点都不能染同一种颜色。
求符合要求的最少的颜色,构造方案。

数据范围

3n2105

题解

显然答案为度数最大的点的度+1
构造方案的话,显然对于一个点来说,它所有的儿子都不能染相同的颜色,并且这些颜色和他自己以及他父亲的颜色都不能相同
dfs的过程中染色即可

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<queue>
#include<ctime>
#include<cmath>
#include<map>
using namespace std;
#define N 200005

int n,x,y,ans;
int d[N],c[N];
int tot,point[N],nxt[N*2],v[N*2];

void add(int x,int y)
{
    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;
}
void dfs(int x,int fa)
{
    int now=0;
    for (int i=point[x];i;i=nxt[i])
        if (v[i]!=fa)
        {
            ++now;
            while (now==c[fa]||now==c[x]) ++now;
            c[v[i]]=now;
        }
    for (int i=point[x];i;i=nxt[i])
        if (v[i]!=fa) dfs(v[i],x);
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i<n;++i)
    {
        scanf("%d%d",&x,&y);
        add(x,y),add(y,x);
        ++d[x];++d[y];
        ans=max(ans,d[x]);ans=max(ans,d[y]);
    }
    printf("%d\n",ans+1);
    c[1]=1;dfs(1,0);
    for (int i=1;i<=n;++i) printf("%d%c",c[i]," \n"[i==n]);
}

D

题意简述

有n个队伍,每一个队伍有一个名字,是两个字符串。
现在需要你给每一个队伍的名字一个缩写,这个缩写的方式有两种:1、第一个字符串的前三个字符 2、第一个字符串的前两个字符+第二个字符串的第一个字符。
如果有一个队伍选择了第二种方式,那么不能存在某一个队伍选择的第一个方式和这个队伍的第一个方式相同。
求一种方案使所有队伍的缩写互异。

数据范围

1n1000 ,每个字符串长度 20

题解

典型的2-sat问题
枚举两支队伍,如果:1、第一种相同,那么必须都选第二种 2、第二种相同,不能同时选第二种 3、第一种和第二种相同,不能同时选第一种和第二种
建图缩点判断可行性
最后dfs构造合法解就行了

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<queue>
#include<ctime>
#include<cmath>
#include<map>
using namespace std;
#define N 1005

int n;char s[N];
struct data{char s[10];}str[N][5];
int tot,point[N*2],nxt[N*N*8],v[N*N*8];
int dfn[N*2],low[N*2],stack[N*2],belong[N*2],ch[N*2],last[N*2],top,dfs_clock,scc;
bool flag,vis[N*2];

bool check(char *a,char *b)
{
    for (int i=1;i<=3;++i)
        if (a[i]!=b[i]) return 0;
    return 1;
}
void add(int x,int y)
{
    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;
}
void tarjan(int x)
{
    dfn[x]=low[x]=++dfs_clock;stack[++top]=x;vis[x]=1;
    for (int i=point[x];i;i=nxt[i])
        if (!dfn[v[i]])
        {
            tarjan(v[i]);
            low[x]=min(low[x],low[v[i]]);
        }
        else if (vis[v[i]])
            low[x]=min(low[x],dfn[v[i]]);
    if (dfn[x]==low[x])
    {
        int now=0;++scc;
        while (now!=x)
        {
            now=stack[top--];
            belong[now]=scc;
            vis[now]=0;
        }
    }
}
void dfs(int x)
{
    vis[x]=1;
    if (ch[x]==-1)
    {
        flag=0;
        return;
    }
    stack[++top]=x;last[x]=ch[x];ch[x]=1;
    if (x<=n) stack[++top]=x+n,last[x+n]=ch[x+n],ch[x+n]=-1;
    else stack[++top]=x-n,last[x-n]=ch[x-n],ch[x-n]=-1;
    for (int i=point[x];i&&flag;i=nxt[i])
        if (!vis[v[i]]) dfs(v[i]);
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;++i)
    {
        scanf("%s",s);str[i][1].s[1]=s[0],str[i][1].s[2]=s[1],str[i][1].s[3]=s[2];
        scanf("%s",s);str[i][2]=str[i][1];str[i][2].s[3]=s[0];
    }
    for (int i=1;i<=n;++i)
        for (int j=i+1;j<=n;++j)
        {
            if (check(str[i][1].s,str[j][1].s))
                add(i,n+i),add(j,n+j);
            if (check(str[i][2].s,str[j][2].s))
                add(n+i,j),add(n+j,i);
            if (check(str[i][1].s,str[j][2].s))
                add(i,j),add(n+j,n+i);
            if (check(str[i][2].s,str[j][1].s))
                add(n+i,n+j),add(j,i);
        }
    for (int i=1;i<=n;++i)
        if (!dfn[i]) tarjan(i);
    for (int i=1;i<=n;++i)
        if (belong[i]==belong[n+i])
        {
            puts("NO");
            return 0;
        }
    for (int i=1;i<=n;++i)
    {
        if (ch[i]) continue;
        memset(vis,0,sizeof(vis));
        top=0;
        flag=1;dfs(i);
        if (!flag)
        {
            for (int j=1;j<=top;++j)
                ch[stack[j]]=last[stack[j]];
        }
    }
    puts("YES");
    for (int i=1;i<=n;++i)
    {
        if (ch[i]==1)
            for (int j=1;j<=3;++j) putchar(str[i][1].s[j]);
        else
            for (int j=1;j<=3;++j) putchar(str[i][2].s[j]);
        puts("");
    }
}

E

题意简述

一个n个点m条边的无向图,问是否能用至多k条路径覆盖每一个点,并且每条路径的长度不超过 2nk

数据范围

1n2105,n1m2105,1kn

题解

这题就是道神经病题。。
dfs过程中记录每一个点的入栈和出栈两次,保证任意的路径不断
然后计算个数输出就行了
题目中每条路径的长度不超过 2nk 的限制保证了这种做法的正确性

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<queue>
#include<ctime>
#include<cmath>
#include<map>
using namespace std;
#define N 400005

int n,m,k,x,y;
int tot,point[N],nxt[N],v[N];
bool vis[N];
int ans[N];

void add(int x,int y)
{
    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;
}
void dfs(int x)
{
    vis[x]=1;ans[++ans[0]]=x;
    for (int i=point[x];i;i=nxt[i])
        if (!vis[v[i]])
        {
            dfs(v[i]);
            ans[++ans[0]]=x;
        }
}
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for (int i=1;i<=m;++i)
    {
        scanf("%d%d",&x,&y);
        add(x,y),add(y,x);
    }
    dfs(1);ans[0]=unique(ans+1,ans+ans[0]+1)-ans-1;
    int now=1;
    for (int i=1;i<=k;++i)
    {
        if (ans[0]-now+1>=(2*n+k-1)/k)
        {
            printf("%d ",(2*n+k-1)/k);
            for (int j=now;j<=now+(2*n+k-1)/k-1;++j) printf("%d%c",ans[j]," \n"[j==now+(2*n+k-1)/k-1]);
            now=now+(2*n+k-1)/k;
        }
        else if (now<=ans[0])
        {
            printf("%d ",ans[0]-now+1);
            for (int j=now;j<=ans[0];++j) printf("%d%c",ans[j]," \n"[j==ans[0]]);
            now=ans[0]+1;
        }
        else puts("1 1");
    }
}

F

题意简述

有一个n个点m条边的有向图,每一条边可能是0或1两种类型。
没有自环,两个点之间也不可能有类型相同的两条边。
构造一个序列,方法是:第一个元素为0,之后的每一次将其复制一遍接到后面然后再将复制的部分取反。
栗子:0,01,0110,01101001…
要求在图上按照这个序列走,问走的路径的最长长度。
如果答案 >1018 ,输出-1

数据范围

1n500,0m2n2

题解

非常强的一道dp。。。
有点类似倍增的思想
f(i,j,k,l)表示走的第一条边类型为i,走的长度为 2j ,起点为k,能走到的点的状态为l 是否有可行的方案
因为是取反,所以dp的时候每一次要反过来接上
求出f了之后如何统计答案呢?
令p表示当前的答案能到达的点,然后枚举每一个点,如果能到达,再用f去更新下一个p
dp的过程中用bitset加速

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<queue>
#include<ctime>
#include<cmath>
#include<bitset>
#include<map>
using namespace std;
#define N 505

int n,m,x,y,z,type;
long long ans;
bitset <N> f[2][65][N],p,q;

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;++i)
    {
        scanf("%d%d%d",&x,&y,&z);
        f[z][0][x][y]=1;
    }
    for (int i=1;i<=60;++i)
        for (int j=1;j<=n;++j)
            for (int k=1;k<=n;++k)
            {
                if (f[0][i-1][j][k]) f[0][i][j]|=f[1][i-1][k];
                if (f[1][i-1][j][k]) f[1][i][j]|=f[0][i-1][k];
            }
    for (int i=1;i<=n;++i)
        if (f[0][60][1][i])
        {
            puts("-1");
            return 0;
        }
    type=0;ans=0LL;p[1]=1;
    for (int i=60;i>=0;--i)
    {
        q.reset();
        for (int j=1;j<=n;++j) if (p[j]) q|=f[type][i][j];
        if (q.any()) type^=1,ans+=1LL<<i,p=q;
        if (ans>1e18) break;
    }
    if (ans>1e18) puts("-1");
    else printf("%I64d\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值