Codeforces Round #637 (Div. 1) / contest 1340



题目地址:https://codeforces.com/contest/1340



A Nastya and Strange Generator

题意

思路

代码

#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
typedef vector<int> VI;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

const int maxn=1e5+5;
int a[maxn],n;

bool can(int l,int r)
{
    REP(i,l+1,r-1) if(a[i]!=a[i-1]+1) return 0;
    return 1;
}

int main()
{
    //freopen("input.txt","r",stdin);
    int T=read();
    while(T--)
    {
        n=read();
        REP(i,1,n) a[i]=read();
        int j=n,cnt=1,last=n+1;
        while(cnt<=n)
        {
            while(j>=1 && a[j]!=cnt) j--;
            if(j<1) break;
            if(!can(j,last)) break;
            cnt+=last-j; last=j;
        }
        puts(cnt>n?"Yes":"No");
    }

    return 0;
}



B Nastya and Scoreboard

题意:我们都知道数码管是什么,也知道亮那些表示什么数字。现在有n个数码管,亮的情况已经给出,问再点亮恰好 k 个灯管所能得到的最大的数是多少?

思路:核心思想在于预先计算一个数组 c[i][j],表示第 i 个数往后总共点亮 j 个灯管能否得到一个合法的数,处理完 c 之后就从前往后贪心就可以了。

代码

#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
typedef vector<int> VI;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

typedef bitset<7> B;
const int maxn=2005;
B a[maxn],b[12];
bool sub(B x,B y) {return (x|y)==y;}
int to[maxn][8],ans[maxn],c[maxn][maxn];

int main()
{
    //freopen("input.txt","r",stdin);
    int n=read(),k=read();
    b[0]=B("1110111"); b[1]=B("0010010"); b[2]=B("1011101"); b[3]=B("1011011");
    b[4]=B("0111010"); b[5]=B("1101011"); b[6]=B("1101111"); b[7]=B("1010010");
    b[8]=B("1111111"); b[9]=B("1111011");
    char s[10];
    REP(i,1,n) scanf("%s",s),a[i]=B(s);
    REP(i,1,n)
    {
        REP(j,0,7) to[i][j]=-1;
        REP(j,0,9) if(sub(a[i],b[j]))
        {
            int x=b[j].count()-a[i].count();
            to[i][x]=max(to[i][x],j);
        }
    }
    c[n+1][0]=1;
    REP_(i,n,1) REP(j,0,k) REP(q,0,7) if(j>=q && to[i][q]>=0 && c[i+1][j-q]) c[i][j]=1;

    REP(i,1,n)
    {
        int maxx=-1,x=0;
        REP(j,0,7) if(to[i][j]>=0 && c[i+1][k-j] && to[i][j]>maxx) maxx=to[i][j],x=j;
        if(maxx<0) return puts("-1"),0;
        ans[i]=maxx; k-=x;
    }
    REP(i,1,n) printf("%d",ans[i]);

    return 0;
}



C Nastya and Unexpected Guest

题意:[0, n] 中有 m 个安全点,其中 0 和 n 一定是安全点(包括在 m 个中),你从 0 开始走路,每单位时间可以走 1,在安全点可以改变方向,非安全点不能改变方向,并且处于绿灯周期时不能在任意地点停留(绿灯时必须一直走)。每个绿灯周期为 g,红灯周期为 r,在绿灯结束红灯开始的时刻,必须停留在安全点。问走到终点 n 的最少时间?

思路:dp+01BFS。设 f[i][j] 表示在第 i 个安全点,绿灯时间还剩 j 的经过的最少绿灯周期数,那么就有点像分层图那样子求最短路就可以了,其中每个 (i, j) (而不是 i )表示一个结点,然后相邻的 (i 和 i+1,i 和 i-1) 之间存在边,当转移之后 j=0 的时候要 +1,其它时候不用。这里用当前最优(队首)去松弛其它结点的时候,因为有可能 +1 或者 不变,所以传统的 BFS(只能处理边权为 1,这里相当于边权可以是 1 也可以是 0),所以要用 01BFS,边权为 1 时被松弛的结点放到队尾,边权为 0 时放到队首。最后统计答案。

代码

#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
typedef vector<int> VI;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

const int M=1e4+5,G=1005,inf=1e9;
int n,m,d[M],g,r,f[M][G];

void BFS()
{
    typedef pair<int,int> P;
    deque<P> Q;
    Q.push_back(P(1,g));
    while(!Q.empty())
    {
        P p=Q.front(); Q.pop_front();
        int i=p.first,j=p.second;
        if(i>1)
        {
            int q=d[i]-d[i-1];
            if(j==q && f[i-1][g]>f[i][j]+1)
                Q.push_back(P(i-1,g)),f[i-1][g]=f[i][j]+1;
            else if(j>q && f[i][j]<f[i-1][j-q])
                Q.push_front(P(i-1,j-q)),f[i-1][j-q]=f[i][j];
        }
        if(i<m)
        {
            int q=d[i+1]-d[i];
            if(j==q && f[i+1][g]>f[i][j]+1)
                Q.push_back(P(i+1,g)),f[i+1][g]=f[i][j]+1;
            else if(j>q && f[i][j]<f[i+1][j-q])
                Q.push_front(P(i+1,j-q)),f[i+1][j-q]=f[i][j];
        }
    }
}

int main()
{
    //freopen("input.txt","r",stdin);
    n=read(),m=read();
    REP(i,1,m) d[i]=read();
    g=read(),r=read();
    REP(i,0,m+1) REP(j,0,g+1) f[i][j]=inf;
    f[1][g]=0;
    sort(d+1,d+m+1);
    BFS();
    int ans=inf<<1;
    REP(j,1,g)
    {
        if(f[m][j]>=inf) continue;
        if(j==g) ans=min(ans,f[m][j]*(g+r)-r);
        else ans=min(ans,f[m][j]*(g+r)+g-j);
    }
    printf("%d",ans>=inf?-1:ans);

    return 0;
}



D Nastya and Time Machine

题意:有一棵树,一开始在 1 号结点,时间为 0。假设当前在 u 号结点,时间为 t,我们记现在的状态为 (u, t),接下来有两种操作:(1)转移到 v 号结点((u, v) is a edge),然后 t 变为 t+1,也就是状态变为 (v, t+1);(2)使用时光机,把状态变为 (u, tt),其中 0 ≤ t t < t 0\le tt < t 0tt<t 。我们的目标是遍历所有结点,并且使得所有状态的时间的最大值最小,并且要求遍历路径中状态不能重复。给出遍历状态路径。

思路:这是一道神奇的构造题。先证明出这个最大的时间一定不小于最大的度数 m,因为假设结点 u 的度为 d u d_u du ,那么一定存在 d u d_u du 个路径是要转移到 u 的,而转移过去之后的时间最小为 1,又不能重复,所以上述不等式成立。我们现在如果可以构造出一种遍历路径,使得恰好时间最大值为 m,那么这个就是答案。

考虑从状态 (u, t) 开始,转移到 (v, t+1),然后遍历完 v 的子树之后再返回到 (u, t+1),那么对于 u 来说,经过一个 t 就可以把一个儿子的子树遍历完。如果对于每个结点都可以这么做,那么这就是一种方案。事实上也是如此:对于每个结点 u,开始访问 u 的时候已经有了一个 t,然后对于每个儿子,给他们分配 t+1, t+2, …,如果分配过程中超过了 m,那么再(使用时光机)回到 m-d[u] 再开始分配。可以发现,如果分配过程中没有超过 m,那么对于结点 u 遍历完所有儿子之后还要使用一次时光机把时间变成 t-1,然后返回父结点变成 t;如果超过了 m,由于是从 m-d[u] 再次往后分配,最后又会回到 t-1 。非常巧妙。

(感觉讲得好乱……)

代码

#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
typedef vector<int> VI;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

const int maxn=1e5+5;
VI G[maxn],ans1,ans2;
int d[maxn],n,m;

void add(int u,int t) {ans1.push_back(u); ans2.push_back(t);}

void dfs(int u,int fa,int t)
{
    add(u,t);
    int tt=t;
    for(int v:G[u]) if(v!=fa)
    {
        tt++;
        if(tt>m) tt=m-d[u],add(u,tt),tt++;
        dfs(v,u,tt);
    }
    if(fa)
    {
        if(tt!=t-1) tt=t-1,add(u,tt);
        add(fa,t);
    }
}

int main()
{
    //freopen("input.txt","r",stdin);
    n=read();
    REP(i,1,n-1)
    {
        int u=read(),v=read();
        G[u].push_back(v); G[v].push_back(u);
        d[u]++; d[v]++;
        m=max(m,max(d[u],d[v]));
    }
    dfs(1,0,0);
    printf("%d\n",ans1.size());
    REP(i,0,ans1.size()-1) printf("%d %d\n",ans1[i],ans2[i]);

    return 0;
}




F

题意

思路

代码


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值