Codeforces Round #397

A. Neverending competitions

There are literally dozens of snooker competitions held each year, and team Jinotega tries to attend them all (for some reason they prefer name “snookah”)! When a competition takes place somewhere far from their hometown, Ivan, Artsem and Konstantin take a flight to the contest and back.

Jinotega’s best friends, team Base have found a list of their itinerary receipts with information about departure and arrival airports. Now they wonder, where is Jinotega now: at home or at some competition far away? They know that:

  • this list contains all Jinotega’s flights in this year (in arbitrary order),
  • Jinotega has only flown from his hometown to a snooker contest and back,
  • after each competition Jinotega flies back home (though they may attend a competition in one place several times),
  • and finally, at the beginning of the year Jinotega was at home.

Please help them to determine Jinotega’s location!

题意

给定若干 Jinotega 的飞行记录,其记录形式为 XXX->YYY 。 Jinotega 的飞行记录仅两种,其一为从家所在的机场前往其它机场,其二为从其它机场返回他的家,且 Jinotega 离开家之后只会使用飞机作为交通工具。问在保证数据合法的情况下,根据给出的 n 条飞行记录判断 Jinotega 在家或在外比赛。已知在最开始的时候, Jinotega 在家。

分析

由于保证数据合法,且 Jinotega 初始时在家,故若 n%2==1 ,Jinotega 在外比赛,反之 Jinotega 在家。

代码

#include<bits/stdc++.h>
using namespace std;
string s;
int main()
{
    int n;
    scanf("%d",&n);
    cin>>s;
    for(int i=0;i<n;i++)    cin>>s;
    printf("%s\n",n % 2 ? "contest" : "home");
}

B. Code obfuscation

Kostya likes Codeforces contests very much. However, he is very disappointed that his solutions are frequently hacked. That’s why he decided to obfuscate (intentionally make less readable) his code before upcoming contest.

To obfuscate the code, Kostya first looks at the first variable name used in his program and replaces all its occurrences with a single symbol a, then he looks at the second variable name that has not been replaced yet, and replaces all its occurrences with b, and so on. Kostya is well-mannered, so he doesn’t use any one-letter names before obfuscation. Moreover, there are at most 26 unique identifiers in his programs.

You are given a list of identifiers of some program with removed spaces and line breaks. Check if this program can be a result of Kostya’s obfuscation.

题意

定义某操作如下:找到一段程序中第一个未修改的变量名,并修改该程序段中所有对应变量名为 a 。之后再顺序寻找第二个未修改的变量名,并将其以及所有对应变量名改为 b 。依此类推,直到所有变量名均被修改。保证原程序中的变量名不会出现单字符变量名,同时最多有 26 个不同的变量名。问若给定修改后的程序(删除变量名之间的空格),问该程序是否可以由某原程序经修改后产生。

分析

本题主要是给定一个正向工程的描述,以及一个正向工程的结果。要求判断该正向工程在转换过程中是否出错。

很显然,询问的字符串的首字母一定为 a ,否则一定错误。第二个字符可以为小于等于之前出现过的最大合法字符+1的任意字符。依此类推。当判断到某个字符不符合上述规则的时候,即可直接判断为出错。

代码

#include<bits/stdc++.h>
using namespace std;
int main()
{
    string s;
    cin>>s;
    char mx = 'a'-1;
    for(int i=0;i<s.size();i++)
    {
        if(s[i] <= (mx + 1))
            mx = max(mx, s[i]);
        else {
            printf("NO\n");
            return 0;
        }
    }
    printf("YES\n");
}

C. Table Tennis Game 2

Misha and Vanya have played several table tennis sets. Each set consists of several serves, each serve is won by one of the players, he receives one point and the loser receives nothing. Once one of the players scores exactly k points, the score is reset and a new set begins.

Across all the sets Misha scored a points in total, and Vanya scored b points. Given this information, determine the maximum number of sets they could have played, or that the situation is impossible.

Note that the game consisted of several complete sets.

题意

Misha 和 Vanya 两人进行了若干局比赛,每局比赛 k 分。

每局比赛 k 分表示两人在比赛中获胜方计 1 分,失败方计 0 分。当其中一人 A 首先达到 k 分的时候,即视作 A 该局比赛胜利。两人比分均归零。开始新的一局比赛。

给定三个整数 k, a 和 b 。其中 a 表示 Misha 在若干局比赛中总共获得的分数, b 表示 Vanya 在若干局比赛中总共获得的分数。问当两人恰好结束最后一局比赛的时候,a 和 b 是否是一组合法的比分。若不是,输出 -1 。若是,请给出两人可能进行的最多局数为多少?

分析

在理解题意后,很显然可以得出,若 a < k 且 b < k ,则一定非法,输出 -1

之后考虑到使比赛局数最多,因此应尽量使每局的比分为 k : 00 : k 。剩下余数 a % k 以及 b % k 。为使比赛恰好完成最后一局。故剩余分数可作为小比分添加到 k : 00 : k 中,使之成为 k : b%ka%k : k 。由于 a % kb % k 小于 k 一定成立,所有比分 k : b%ka%k : k 都是合法的一局中的比分。

仅考虑上述情况给出的代码。虽然看似合理,但却存在漏洞,这也就成了本题中一个重要的 Hack 点。本人在比赛中 10 successful hack 。寥以为情人节最大彩蛋。

该组数据 12 13 1 可作为上述结论的一大问题所在。当 b < k 时,虽然 a % k == 1 非零,但 b 此时无法提供 k 分以组成一局 a%k : k 。反之亦然,故当 b < k 且 a % k != 0 以及 a < k 且 b % k != 0 两种情况也为非法,输出 -1

至此,此题方可完美通关。

代码

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int k, a, b;
    scanf("%d %d %d",&k,&a,&b);
    if(a < k && b < k){ //此判断已包含在下列两个判断中,可不写。
        printf("-1");
        return 0;
    }
    if(b < k && a % k){
        printf("-1");
        return 0;
    }
    if(a < k && b % k){
        printf("-1");
        return 0;
    }
    int ans = a / k + b / k;
    printf("%d\n",ans);
}

D. Artsem and Saunders

Artsem has a friend Saunders from University of Chicago. Saunders presented him with the following problem.

Let [n] denote the set {1, …, n}. We will also write f: [x] → [y] when a function f is defined in integer points 1, …, x, and all its values are integers from 1 to y.

Now then, you are given a function f: [n] → [n]. Your task is to find a positive integer m, and two functions g: [n] → [m], h: [m] → [n], such that g(h(x)) = x for all x[m] , and h(g(x)) = f(x) for all x[n] , or determine that finding these is impossible.

题意

此题首先定义 [n] 表示集合 {1, …, n} 。 f:[x][y] 被视作是数 1, …, x 对元素 1, …, y的映射。

现在给定 f:[n][n] ,求使得 g(h(x))=x h(g(x))=f(x) 同时成立的两个映射 g:[n][m] h:[m][n] 。若无法构造,答案输出 -1 。若可构造,首先输出 m 的大小,后输出 g[1],g[2],...,g[n] 以及 h[1],h[2],...,h[m]

此题题意颇为难以理解,若无法理解我的解释,可对照英文原题加以理解。

分析

​此题始一接触,确实难以下手,特别是对于某些本来就对映射关系感到头疼的人。貌似想直接放弃,奈何后面的题目也好难 :cry:

此题对思路的产生难以描述,仅说明做法。首先需构造一个 f(x)h(x) 的映射 h2f( f(x) ) ,若 h2f( f(x) ) 存在,则根据 h( g(x) )=f(x) , 可知 g(x)=h2f( f(x) ) ;若 h2f( f(x) ) 不存在,则通过 ++m h2f( f(x) ) 赋值,同时处理对应的 g(x) h(x)

在处理完上述 g(x) h(x) 后,通过剩下的未使用的条件 g( h(x) )=x 是否合法判断上面得出的 g(x) h(x) 是否合法。

​具体不做证明,貌似也不会严格证明 :smile: 。

代码

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 10;
int f[MAXN], g[MAXN], h[MAXN];
int h2f[MAXN];
int n,m = 0;
bool jud()
{
    for(int i=1;i<=m;i++)
    {
        if(h[i] > n)    return false;
        if(g[h[i]] != i)    return false;
    }
    return true;
}
int main()
{
    scanf("%d",&n);
    memset(h2f, 0, sizeof(h2f));
    for(int i=1;i<=n;i++){
        scanf("%d",&f[i]);
        if(h2f[ f[i] ])
            g[i] = h2f[ f[i] ];
        else{
            h2f[ f[i] ] = ++m;
            g[i] = m;
            h[m] = f[i];
        }
    }
    if(!jud()){
        printf("-1\n");
        return 0;
    }
    printf("%d\n",m);
    for(int i=1;i<=n;i++)
        if(i == n)  printf("%d\n",g[i]);
        else    printf("%d ",g[i]);
    for(int i=1;i<=m;i++)
        if(i == m)  printf("%d\n",h[i]);
        else    printf("%d ",h[i]);
}

E. Tree Folding

Vanya wants to minimize a tree. He can perform the following operation multiple times: choose a vertex v, and two disjoint (except for v) paths of equal length a0=v,a1,...,ak , and b0=v,b1,...,bk . Additionally, vertices a1,...,ak,b1,...,bk must not have any neighbours in the tree other than adjacent vertices of corresponding paths. After that, one of the paths may be merged into the other, that is, the vertices b1,,bk $ can be effectively erased:

img

Help Vanya determine if it possible to make the tree into a path via a sequence of described operations, and if the answer is positive, also determine the shortest length of such path.

题意

此题给出一种操作 merge-erase 。当以 v 为子树树根,存在两根树枝 a1,...,ak b1,...,bk 两树枝上的点的数量相同,且树枝上的点除两个叶节点 ak bk 外,均只与相邻两节点相连,即如 ai 只与 ai1, ai+1 相连。此时,两树枝可进行合并,即删除其中任意一条树枝及其上所有的点。同时,任意树根只要有满足上述条件的树枝,可进行任意次 merge-erase 操作。

问给定树 G = (V, E) 且 |V| = |E|+1 。能否在进行任意次 merge-erase 操作后,使得该树退化成一条链。若不能,输出 -1 ;若能,给出所产生的最短的链所包含的边数。

分析

此题难点在于如何寻找一种必然可靠的判断方案。

比较容易想到的是,对于一个无根树,找到其直径,则直径上的点向外产生的树枝长度必然小于等于该点到两端点的最小距离。而当以某一点为根的子树在退变成链后,若仍存在多条不同长度的链,则无法进行 merge-erase ,则输出 -1 并结束。

由于该树的直径上的中点,因其上向外产生的树枝,若其长度与该点到直径端点的距离不同,仍可将两半径 merge-erase 而留下该树枝作为链的另一端。

因此,综合上面所提出的想法。首先判断建树,判断树的直径以及直径的两个端点。之后,再一遍 dfs 找到直径的中心。再以该中心为树的根,依照上述说明对树进行判断,其是否能退化为链。

代码

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int n, dep[N], center, ans, son[N];
bool is_diameter[N];
vector<int> vec[N];
pair<int, int> p, q;    //.first 点标号, .second 深度
void dfs(int rt, int fa, int stp, pair<int,int>& pot)
{
    if(stp > pot.second)    pot.first = rt, pot.second = stp;
    dep[rt] = stp;
    for(int i=0,to;i<vec[rt].size();i++)
    {
        to = vec[rt][i];
        if(to == fa)    continue;
        dfs(to, rt, stp+1, pot);
        if(is_diameter[to]) is_diameter[rt] = 1;
    }
}
bool DFS(int rt, int fa, int stp)
{
    set<int> st;
    if(vec[rt].size() == 1) son[rt] = 1;
    for(int i=0, to;i<vec[rt].size();i++)
    {
        to = vec[rt][i];
        if(to == fa)    continue;
        if(DFS(to, rt, stp+1) == false) return false;
        st.insert(son[to]);
        son[rt] = son[to]+1;
    }
    if(fa == -1)
    {
        if(st.size() <= 2){
            for(set<int>::iterator it=st.begin();it!=st.end();it++)
                ans += *it;
            return true;
        }
        return false;
    }
    else
        return st.size() <= 1;
}
int main()
{
    scanf("%d",&n);
    for(int i=1,u,v;i<n;i++)
    {
        scanf("%d %d",&u,&v);
        vec[u].push_back(v);
        vec[v].push_back(u);
    }
    p.second = q.second = 0;
    for(int i=1;i<=n;i++)
        if(vec[i].size() == 1)  
        {
            dfs(i, -1, 0, p);   //找到树的任意一个叶子节点,准备寻找树直径上的一个端点
            break;
        }
    dfs(p.first, -1, 0, q); //以直径上一个端点i来获取直径上另一端点p.first。
    memset(is_diameter, 0, sizeof(is_diameter));
    is_diameter[q.first] = 1;
    dfs(p.first, -1, 0, q); //通过两个端点p.first , q.first 获取直径的中心。
    for(int i=1;i<=n;i++)
        if(dep[i] == q.second / 2 && is_diameter[i] == 1)
        {
            center = i;
            break;
        }
    bool flg = DFS(center, -1, 0);  //判断是否能退化为链
    while(ans && ans % 2 == 0) ans /= 2;
    printf("%d\n",flg ? ans : -1);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值