Codeforces Round #620 (Div. 2)(A~E)

A. Two Rabbits(水题)

代码

#include<iostream>
#include<stdio.h> 
#include<stdlib.h>  
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<string>
#include<queue>
#include<map>
void fre() { freopen("A.txt","r",stdin); freopen("Ans.txt","w", stdout); }
using namespace std;

#define ll long long 
#define db double
#define INF 0x3f3f3f3f
const int mod =998244353;
const int mxn = 2e5 + 10;
int ar[mxn];
int br[mxn];

int main()
{
    //fre();
    int t;
    scanf("%d", &t);
    while(t --)
    {
        int x, y, a, b;
        scanf("%d %d %d %d", &x, &y, &a, &b);
        int cha = y - x;
        if(cha % (a + b) == 0)
        {
            printf("%d\n", cha / (a + b));
        }
        else
            printf("-1\n");
    }


    return 0;
}


B. Longest Palindrome

思路

  • 题意: 给我们n个长度为m的字符串,我们可以任选其中的一些字符串进行拼接,但是要求这个拼接出来的字符串必须是 会问字符串,问我们能拼接的最长回文串是多长?

  • 分析: 这题的题意很简单,看数据范围 1 < = m < = 100 , 1 < = m < = 50 1<=m<=100, 1<=m<=50 ,明显可以暴力通过,剩下的就是找一个暴力的方案,首先要想形成回文字符串,那么在我们拼凑出的会问串中,处于对称位置的字符串x,y的要求是 其中一个颠倒一下顺序,就变成另一个字符串了,那那么我们就利用这个这个关系,并且用map<string,int> 来统计都有哪些字符串出现过,当我们讨论到某个字符串x的时候,这个时候把x颠倒一下设为y,这个时候通过查询y是出现过,如果出现过,那么x,y就可以放到对称位置了

代码

#include<iostream>
#include<stdio.h> 
#include<stdlib.h>  
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<string>
#include<queue>
#include<map>
void fre() { freopen("A.txt","r",stdin); freopen("Ans.txt","w", stdout); }
using namespace std;

#define ll long long 
#define db double
#define INF 0x3f3f3f3f
const int mod =998244353;
const int mxn = 50 + 10;

map<string, int> mp;


int main()
{
    /* fre(); */
    ios::sync_with_stdio(false);
    int n, m;
    cin >> n >> m;
    string s, rs;
    string pre, suf;
    for(int i = 1; i <= n; i ++)
    {
        cin >> s;
        mp[s] ++;
        rs = s;
        reverse(rs.begin(), rs.end());
        if(mp[rs] > 0 && s != rs)
        {
            pre += s;
            suf = rs + suf;
            mp[rs] --;
            mp[s]--;
        }
    }

    string mid;
    for(auto x : mp)
    {
        if(x.second > 0)
        {
            string s = x.first;
            rs = s;
            reverse(rs.begin(), rs.end());
            if(s == rs)
            {
                if(mid.size() < s.size())  
                    mid = s;
            }
        }
    }
    string ans = pre + mid + suf;
    cout << ans.size() << endl;
    cout << ans << endl;
    return 0;
}


C. Air Conditioner(维护区间 + 思维)

思路

  • 题意:一家饭店有个空调,可以调节室内温度,对于一个单位时间可以让室内的温度上升1度、下降1度或者温度保持不变,现在给我们当时间为0的时候度初始室温 m,现在有n个人按时间顺序从前往后一次来到这个酒店,对于某个人有三个属性 t、l、r 分别表示他来的时间、他希望刚到饭店时饭店内的温度位于[l,r]范围,如果他到时,饭店的室温不在这个范围,他就会不满意,问我们通过调节利用空调调节温度来是n位客人都满意

  • 分析:刚开始做的时候是没有一点思路,后来一看的到 “维护温度改变区间,并将这个维护之后的温度区间当前顾客希望的温度区间进行比较,看是否有重叠,如果没有重叠那就直接输出NO,知道最后都满足的话输出YES”,看到这些之后就懂了

代码

#include<iostream>
#include<stdio.h> 
#include<stdlib.h>  
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<string>
#include<queue>
#include<map>
void fre() { freopen("A.txt","r",stdin); freopen("Ans.txt","w", stdout); }
#define ios ios::sync_with_stdio(false) 
using namespace std;

#define ll long long 
#define db double
#define INF 0x3f3f3f3f
const int mod =998244353;
const int mxn = 2e5 + 10;

ll a[mxn], b[mxn], c[mxn];

int main()
{
    /* fre(); */
    /* ios; */
    int T;
    scanf("%d", &T);
    while(T --)
    {
        ll n, m;
        scanf("%lld %lld", &n, &m);
        for(int i = 1; i <= n; i ++)
            scanf("%lld %lld %lld", &a[i], &b[i], &c[i]);
        ll l = m, r = m;
        ll cha = a[1];
        int fg = 1;
        for(int i = 1; i <= n; i ++)
        {
            if(i != 1) cha = a[i] - a[i - 1];
            r+= cha; l -= cha;
            r = min(r, c[i]);
            l = max(l, b[i]);
            if(l > r)
            {
                fg = 0;
                break;
            }
        }

        if(fg)
            printf("YES\n");
        else
            printf("NO\n");
    }

    return 0;
}


D. Shortest and Longest LIS(思维+贪心)

思路

  • 题意:给我们一个长度为n-1,仅有 ‘<’、‘>’组成的字符串s,现在让我们来构造一个长度为n且元素为1,2,3,,,n,的一个排列ar,对于这个排列ar中的要求是如果s[i] 位置的字符是’<’,那么要求 a r [ i ] < a r [ i + 1 ] ar[i]<ar[i+1] ,如果s[i]位置的字符是’>’,那么要求 a r [ i ] > a r [ i + 1 ] ar[i]>ar[i+1] ,现在让我们构造出一个长度最短的递增子序列ar,在构造一个长度最长的递增子序列br, 并输出他们

  • 分析

  1. 我们先分析最长的递增子序列怎么构造,显然要想构造出来的队列 拥有最长的递增子序列,那么大的数尽可能的往后考,小的仅可能的往前,我们不妨设 要构造的序列是 7 > > < > > < 7 >><>>< ,我可以让初始的排列为:1 2 3 4 5 6 7, 这个是最理想的的结果,但是我们要根据限制条件进行稍微的修改,对s中的‘<’号限制条件可以不考虑,因为在我们最初始的排列是都符合这个限制的条件的;对于 '>'号,我们要那么我们需要交换初始排列中对应位置和下一个位置的数值进行交换,这样就可以满足这个条件了,如果是多个连续的‘>’, 的大于字符,那么我们就将对应位置的连续字符串进行颠倒,,,这样一直处理从左到右处理完所有s中‘>’ 大于号之后,就能得到答案了,首先我们要明白这种做法为什么可行??应为对于最初的理想排列 1 , 2 , 3 , 4 , 5 , 6 , 7 1,2,3,4,5,6,7 ,我们要对某个区间进行颠倒以符合限制条件,就会是递增子序列的长度变短,而进行的颠倒次数越少,我们得到的递增子序列就越长,那么颠倒次数最少是多少,那就是s的固定限制条件都满足就是最少次数了
  2. 对于最短递增子序列的构造,与最长子序列的构造思想一模一样,只不过我们设最开始理想的队列是 7 , 6 , 5 , 4 , 3 , 2 , 1 7, 6, 5, 4, 3, 2, 1 , 我们在讨论的时候只需要关注‘<’限制条件进行颠倒就行了,而“>”号的限制调经理想队列已经满足了,而且 在进行颠倒操作的时候,也不会影响原来‘>’的限制关系

代码

#include<iostream>
#include<stdio.h> 
#include<stdlib.h>  
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<string>
#include<queue>
#include<map>
void fre() { freopen("A.txt","r",stdin); freopen("Ans.txt","w", stdout); }
#define ios ios::sync_with_stdio(false) 
using namespace std;

#define ll long long 
#define db double
#define INF 0x3f3f3f3f
const int mod =998244353;
const int mxn = 2e5 + 10;

char ar[mxn];
int ml[mxn];
int ms[mxn];

void reverse(int l, int r, int m[])
{
    for(int i = l, t = 0; i <= (l + r)/2; i ++, t ++)
        swap(m[l + t], m[r - t]);
}


int main()
{
    /* fre(); */
    /* ios; */
    int T;
    scanf("%d", &T);
    while(T --)
    {
        int n;
        scanf("%d %s", &n, ar + 1);
        for(int i = 1; i <= n; i ++)
        {
            ml[i] = i;
            ms[i] = n - i + 1;
        }
        
        //求最短递增子序列
        int last = 1;
        for(int i = 1; i <= n; i ++)
        {
            if(ar[i] == '>')
            {
                if(i - last >= 1)
                    reverse(last, i, ms);
                last = i + 1;
            }
        }
        if(last != n)
            reverse(last, n, ms);

        //求最短递增子序列
        last = 1;
        for(int i = 1; i <= n; i ++)
        {
            if(ar[i] == '<')
            {
                if(i - last >= 1)
                    reverse(last, i, ml);
                last = i + 1;
            }
        }
        if(last != n)
            reverse(last, n, ml);

        for(int i = 1; i <= n; i ++)
            printf("%d ", ms[i]);
        printf("\n");
        for(int i = 1; i <= n; i ++)
            printf("%d ", ml[i]);
        printf("\n");
    }

    return 0;
}

E. 1-Trees and Queries(LCA最近公共祖先 + 倍增优化+思维)

在这里插入图片描述

铺垫知识:
1.倍增
2.LCA(最近公共祖先)

思路

  • 题意:给我们一个以1为根结点,有n个节点树,有q次询问,对于每次询问有四个变量:x, y, a, b, k, 表示的意思是在x、y节点之间建立一条无向边,这个时候就从“树”变成“图”数据结构了,问从 a 到 b是否存在一条路径的长度为 k,(图中每条边的长度为1),注意:对于某条路径可以重复包含相同的节点和边–>翻译过来的意思是:对于某一条路径可以重复包含某一条边,如果存在这一条路径输出YES

  • 分析:看数据范围 1 < = n < = 1 e 5 ,   q < = 1 e 5 1<=n<=1e5,~q<=1e5 ,程序运行时间给了4s,可以明显看出数据量很大,特别对于1e5次询问,明显需要一个非常棒的预处理,,,现在分析题目的需求,对于某次询问:

  1. 我们先不添加x--y这条边,那么a->b明显只有一条路路径可
  2. 而如果我们添加了x--y这条边这个时候的,这个时候a到b有多了两条路径a->x->y->b,`a->y->x->b
    也就是我们总共讨论到路径就3条,有因为每条路径中的边可以重复走, 以条路径可以变化出无数条路径,我们可观察到:我们如果重复走某一条边那么路径的长度必定是最路径的长度基础上加2的倍数,
    有了上们的观察,我们得出结论 a到b的三条路径只要有一条最短路径长度小于<=k,并且与 k同奇偶性,就一定可以得到YES,那么问题就转换成了对于每次询问我们快速的找出a到b 的三条路径的最短路径长度,,,,怎么找呢??那就要用到我们神奇的LCA了,通过它我们可以找到任意两几节点a、b的最近公共祖先设为c,我们设某个节点i的深度为deep[i], 到b的最短路径长度= depth[a] - depth[c]+depth[b]-depth[c],同理我们即如要求a->x->y->b的最短路径的长度,可以拆分为三段来求a -> x长度通过公共祖先之间的深度差来求、x->y长度为1、y->b长度通过公共祖先之间的深度差来求。。。。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<map>
void fre() { freopen("A.txt", "r", stdin); freopen("Ans.txt", "w", stdout); }
using namespace std;

const int mxn = 2e5 + 10;
const int lca = 20;
struct Edge
{
    int v, w, last;
} edge[mxn];
int head[mxn], cnt = 0;

void Add(int u, int v, int w)
{
    edge[++ cnt] = (Edge){ v, w, head[u] };     //注意是 ++cnt;
    head[u] = cnt;
}
int anc[mxn][lca], depth[mxn];

void dfs(int u, int p, int d)
{
    anc[u][0] = p; depth[u] = d;
    for(int i = head[u]; i; i = edge[i].last)
    {
        int v = edge[i].v;
        if(v == p) continue;
        dfs(v, u, d + 1);
    }
}

void Init(int root, int n)      //汇总预处理
{
    dfs(root, 0, 1);
    for(int j = 1; j < lca; j ++)
        for(int i = 1; i <= n; i ++)
            anc[i][j] = anc[anc[i][j - 1]][j - 1];
}

void swim(int & x, int h)         //让x节点往上爬h高度, 注意这里的 x 变量要使用 "引用"
{
    for(int i = 0; h; i ++)
    {
        if(h & 1)
            x = anc[x][i];
        h >>= 1;
    }
}


int Lca(int  x, int y)
{
    if(depth[x] < depth[y]) swap(x, y);
    swim(x, depth[x] - depth[y]);       //让x节点往上爬
    if(x == y) return x;                //如果x节点爬完之后与y节点相同的话,直接返回x
    
    for(int i = lca - 1; i >= 0; i --)  //从大到小枚举
    {
        if(anc[x][i] != anc[y][i])
        {
            x = anc[x][i];
            y = anc[y][i];
        }
    }
    return anc[x][0];
}

int get_dis(int x, int y)
{
    int ver = Lca(x, y);
    return depth[x] + depth[y] - depth[ver] * 2;
}

bool check(int dis, int k)
{
    return dis <= k && (dis % 2 == k % 2);
}

int main()
{
    /* fre(); */
    int n;
    scanf("%d", &n);
    int u, v;
    for(int i = 1; i < n; i ++)
    {
        scanf("%d %d", &u, &v);
        Add(u, v, 1);
        Add(v, u, 1);
    }
    Init(1, n);

    int q;
    scanf("%d", &q);
    int a, b, x, y, k;
    while(q --)
    {
        scanf("%d %d %d %d %d", &x, &y, &a, &b, &k);
        int dis = get_dis(a, b);
        if(check(dis, k))
        {
            printf("YES\n");
            continue;
        }
        
        dis = get_dis(a, x) + get_dis(b, y) + 1;
        if(check(dis, k))
        {
            printf("YES\n");
            continue;
        }

        dis = get_dis(a, y) + get_dis(b, x) + 1;
        if(check(dis, k))
        {
            printf("YES\n");
            continue;
        }

        printf("NO\n");
    }

    return 0;
}


原创文章 220 获赞 292 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_34261446/article/details/106142063