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<=501<=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] 位置的字符是’<’,那麼要求ar[i]<ar[i+1]ar[i]<ar[i+1],如果s[i]位置的字符是’>’,那麼要求ar[i]>ar[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,71,2,3,4,5,6,7,我們要對某個區間進行顛倒以符合限制條件,就會是遞增子序列的長度變短,而進行的顛倒次數越少,我們得到的遞增子序列就越長,那麼顛倒次數最少是多少,那就是s的固定限制條件都滿足就是最少次數了
  2. 對於最短遞增子序列的構造,與最長子序列的構造思想一模一樣,只不過我們設最開始理想的隊列是7,6,5,4,3,2,17, 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<=1e5, q<=1e51<=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;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章