Codeforces #639 div2 A - F 解题报告

A. Puzzle Pieces

\(Description:\)

  是否可以拼成 \(n \times m\) 的矩形的拼图?

\(Solve:\)

  两个拼图的连接处需要一个凹槽,那么计算一下 \(n \times m\) 的 矩形拼图需要几个凹槽,再跟总凹槽数即 \(n \times m\) 比较一下即可。

\(Code:\)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

int main(){
    int t; cin >> t;
    while(t --){
        ll n, m;
        cin >> n >> m;
        if(n * m < (n - 1) * m + (m - 1) * n)
            puts("NO");
        else puts("YES");
    }
    return 0;
}

B. Card Constructions

\(Description:\)

  你有 \(n\) 张牌来搭建金字塔,搭建能搭建最高的,问可以搭建几座金字塔?

\(Solve:\)

  预处理出搭建不同高度的金字塔需要的卡牌,然后直接二分即可。

\(Code:\)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 1e9;

vector<int> vec;

void init(){
    vec.push_back(0);
    int a = 2, b = 0;
    while(1){
        ll t = a + b * 3;
        if(t <= INF && t > 0) vec.push_back(t);
        else break;
        b += a / 2;
        a += 2;
    }
}

int main(){
    init();
    int t; cin >> t;
    while(t --){
        int n;
        cin >> n;
        int ans = 0;
        while(n){
            auto it = upper_bound(vec.begin(), vec.end(), n);
            it --;
            if(it == vec.begin()) break;
            n -= *it;
            ans ++;
        }
        cout << ans << endl;
    }
    return 0;
}

C. Hilbert's Hotel

\(Description:\)

  给你包含 \(n\)个数的数组 \(a\),问是否存在 \(i + a_{i\ mod\ n} = j + a_{j\ mod\ n},(i,j \in Z)\)?存在输出 \(NO\),不存在输出 \(YES\).

\(Solve:\)

  如果存在,那么 \(i + a_{i\ mod\ n} = j + a_{j\ mod\ n} + kn(k \in Z)\),显然两边在 \(mod\ n\) 的情况下是相等的,所以我们只需要算出 \([0, n-1]\) 内的数(因为 \(((i+n)+a_{(i+n)\ mod\ n})\ mod\ n = (i + a_{i\ mod\ n})\ mod\ n\)),看是否存在相同的数即可。

\(Code:\)

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;

int a[N];

int main(){
    map<pair<int, int>, int> m1;
    m1[{1, 2}] = 1;
    cout << m1.count({1, 2}) << endl;

    int t; cin >> t;
    while(t --){
        int n; cin >> n;
        for(int i = 0; i < n; i ++) scanf("%d", &a[i]);
        map<int, int> m;
        int mark = 0;
        for(int i = 0; i < n; i ++){
            int t = ((i + a[i]) % n + n) % n;
            if(m[t]){
                mark = 1;
                break;
            }
            m[t] = 1;
        }
        if(mark == 1) puts("NO");
        else puts("YES");
    }
    return 0;
}

D. Monopole Magnets

\(Description:\)

  每行,每列至少有一个 \(S\),如果 \(N\)\(S\) 在同行或同列中,并且不在同一格,那么 \(N\) 可以向 \(S\) 移动一格。\(N\) 可以走到所有黑格,并且不能走到白格,求最少的 \(N\) ?

\(Solve:\)

  显然在一个黑格的连通块之内,只需要一个 \(N\),每个黑格都放上一个 \(S\) 就可以走完该连通块的所有黑格,所以我们求出黑格的连通块的个数就是最后的答案。

  在此之前,我们要把无解的情况筛掉。易得出合法的条件是:

  1.一行和一列的黑格必须是连续的;(不连续 \(N\) 就会走到之间的白格上)
  2.如果存在全为白格的行,那么也必须存在全为白格的列.( \(N\) 也会走到白格)

\(Code:\)

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
typedef pair<int, int> PII;

int n, m;
char s[N][N];
int row[N], col[N];
PII X[N], Y[N];

int to[4][2] = {{0, 1}, {-1, 0}, {0, -1}, {1, 0}};

/*
* 1.两个黑格之间不能有白格
* 2.有全为白格得行(列),也必须有列(行)全为白格
*/


bool check(){
   // 预处理出每行每列有多少黑格,以及每行每列黑格的起点和终点
   memset(row, 0, sizeof row);
   memset(col, 0, sizeof col);
   for(int i = 1; i <= n; i ++){
       int mark = 0;
       X[i] = {0, 0};
       for(int j = 1; j <= m; j ++){
           if(s[i][j] == '#'){
               row[i] ++;
               if(mark == 0){
                   X[i] = {j, j};
                   mark = 1;
               }else{
                   X[i].second = j;
               }
           }
       }
   }
   for(int j = 1; j <= m; j ++){
       int mark = 0;
       Y[j] = {0, 0};
       for(int i = 1; i <= n; i ++){
           if(s[i][j] == '#'){
               col[j] ++;
               if(mark == 0){
                   Y[j] = {i, i};
                   mark = 1;
               }else{
                   Y[j].second = i;
               }
           }
       }
   }

   int mark1 = 0; // 是否有全为白格的行
   for(int i = 1; i <= n; i ++){
       if(row[i] == 0) mark1 = 1;
       else if(row[i] != X[i].second - X[i].first + 1)
           return false;
   }

   int mark2 = 0; // 是否有全为白格的列
   for(int j = 1; j <= m; j ++){
       if(col[j] == 0) mark2 = 1;
       else if(col[j] != Y[j].second - Y[j].first + 1)
           return false;
   }

   if(mark2 + mark1 == 1) return false;
   return true;
   
}

void dfs(int x, int y){
   s[x][y] = '.';
   for(int i = 0; i < 4; i ++){
       int xx = x + to[i][0];
       int yy = y + to[i][1];
       if(xx >= 1 && xx <= n && yy >= 1 && yy <= m && s[xx][yy] == '#')
           dfs(xx, yy);
   }
}

int main(){
   cin >> n >> m;
   for(int i = 1; i <= n; i ++)
       scanf("%s", s[i] + 1);
   if(check() == false) 
       {puts("-1"); return 0; }
   int cnt = 0;
   for(int i = 1; i <= n; i ++)
       for(int j = 1; j <= m; j ++)
           if(s[i][j] == '#'){
               cnt ++;
               dfs(i, j);
           }
   cout << cnt << endl;
   return 0;
}

E. Quantifier Question

\(Description:\)

  长为 \(n\) 的数组 \(x\),给出 \(m\)\(x_i\ x_j\) 的关系,意为 \(x_i < x_j\),每个 \(x_i\) 都有一个限定符号 \(Q_i\)\(Q_i \in (\exists, \forall)\) ),要求满足:

\[Q_1x_1,Q_2x_2,Q_3x_3...Q_nx_n,(x_{i1}<x_{j1})\wedge(x_{i2}<x_{j2})...\wedge(x_{im}<x_{jm}) = true \]

  求 \(\forall\) 最多可以就几个,并输出对应的方案。如果没有方案可以满足,就输出 \(-1\) .

\(Solve:\)

  显然由于小于关系具有传递性,所以我们可以把所有关系整合成图。

  \(x_i < x_j\) 就是 \(x_i\)\(x_j\) 的一条有向边,根据这个我们可以建出有向图。显然图里存在环的时候是无解的。

  理由:有环就代表存在如下关系:\(x_i < x_j, x_j < x_k, x_k < x_i\),显然是无解的。

  接下来考虑怎么填符号。一个很重要的信息是:限定符号是从 \(x_1,x_2,...,x_n\) 的。例如:\(x_1 < x_2\),我们给出一种方案:\(\exists x_1 \forall x_2\),解读是:存在一个 \(x_1\) 小于任意一个 \(x_2\) ,显然是错误的。即先定下了 \(x_1\) 的值,再去定之后的值。那么我们的 \(\forall\) 符号只有给对于一个连串的关系中下标最小的。那么映射到有向图中,就是如果一个点所有前驱的最小值和所有后继的最小值大于他本身,那么说明他就是最小的,即可以给他 \(\forall\)

  用拓扑排序可以检查是否有环,并且帮助我们之后找前驱和后继,另外在找前驱的时候要用到的信息是有向边的起点,所以我们还需要反向建图。

\(Code:\)

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;

int n, m;

// g1 正向图,g2 反向图
vector<int> g1[N], g2[N];
int d[N]; // 每个点的入度

int pre[N], nxt[N]; // 最小前驱,最小后继

char ans[N];

// 求拓扑排序
bool tuopu(vector<int> &vec){
    for(int i = 1; i <= n; i ++){
        if(d[i] == 0) vec.push_back(i);
    }
    for(int i = 0; i < vec.size(); i ++){
        int u = vec[i];
        for(int j = 0; j < g1[u].size(); j ++){
            int v = g1[u][j];
            d[v] --;
            if(d[v] == 0) vec.push_back(v);
        }
    }
    return vec.size() == n;
}

int main(){
    cin >> n >> m;
    for(int i = 1; i <= m; i ++){
        int x, y; scanf("%d%d", &x, &y);
        g1[x].push_back(y);
        g2[y].push_back(x);
        d[y] ++;
    }
    vector<int> tp; // 拓扑序列
    if(tuopu(tp) == false) 
        { puts("-1"); return 0; }
    
    for(int i = 1; i <= n; i ++){
        pre[i] = nxt[i] = i; // 初始化
    }

    // 找前驱
    for(int i = 0; i < tp.size(); i ++){
        int u = tp[i];
        for(int j = 0; j < g2[u].size(); j ++){
            int v = g2[u][j];
            pre[u] = min(pre[u], pre[v]);
        }
    }

    // 找后继
    for(int i = tp.size() - 1; i >= 0; i --){
        int u = tp[i];
        for(int j = 0; j < g1[u].size(); j ++){
            int v = g1[u][j];
            nxt[u] = min(nxt[u], nxt[v]);
        }
    }

    int cnt = 0;
    for(int i = 1; i <= n; i ++){
        if(min(nxt[i], pre[i]) == i){
            ans[i] = 'A';
            cnt ++;
        }
        else ans[i] = 'E';
    }
    ans[n + 1] = '\0';

    cout << cnt << endl;
    printf("%s\n", ans + 1);
    
    return 0;
}

F. Résumé Review

\(Description:\)

  给定一个长度为 \(n\) 的数组 \(a\),再给定 \(k\)

  满足下列条件:

   1. \(0 \leq b_i \leq a_i\)

  2. \(\sum_{i = 1}^{n}b_i = k\)

  使得 \(f(b_1,b_2,...,b_n) = \sum_{i = 1}^{n}b_i(a_i - b_i^2)\) 最大,输出 \(b\) 数组。

\(Solve:\)

  参考的博文

  首先对于任意一个 \(i\),我们假设 \(\Delta x = x(a_i - x^2) - (x-1)(a_i - (x-1)^2) = a_i - 3 \times x^2 + 3 \times x - 1\)。意为对于 \(i\) 来说,\(b_i\) 相差 \(1\) 的增量。通过观察我们发现他是递减的,并且他只与 \(a_i\) 有关,由于 \(x\) 只能是整数,所以是散点图,那么我们将图画出来:

  二分图中的横线,理由是我们可以发现点越高,那么他的贡献就会越大,那么只要横线上的点的个数 \(\geq k\) 就是合法的,最后我们再减去多余的点,最后处于横线上的点的贡献是一样的,所以随意减即可。对于任意一个 \(i\)\(\Delta x_1 + \Delta x_2 + ... + \Delta x_{b_i}\) 就是他的总贡献。

\(Code:\)

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
typedef long long ll;

ll n, k;
ll a[N], b[N];

ll cal(ll a, ll x){
    return a - 3 * x * x + 3 * x - 1;
}

bool check(ll Y){
    ll cnt = 0;
    for(int i = 1; i <= n; i ++){
        int l = 0, r = a[i];
        while(l <= r){
            int mid = l + r >> 1;
            if(cal(a[i], mid) >= Y)  l = mid + 1;
            else r = mid - 1;
        }
        b[i] = l - 1;
        cnt += b[i];
    }
    return cnt >= k;
}

int main(){
    cin >> n >> k;
    for(int i = 1; i <= n; i ++)
        scanf("%lld", &a[i]);
    ll l = -4e18, r = 4e18, Y;
    while(l <= r){
        ll mid = l + r >> 1;
        if(check(mid)){
            // cout << "mid = " << mid << endl;
            Y = mid;
            l = mid + 1;
        }
        else r = mid - 1;
    }
    check(Y);
    ll cnt = 0;
    for(int i = 1; i <= n; i ++) cnt += b[i];
   
    cnt -= k;
    for(int i = 1; i <= n && cnt; i ++){
        if(b[i] && cal(a[i], b[i]) == Y){
            cnt --; b[i] --;
        }
    }

    for(int i = 1; i <= n; i ++)
        printf("%lld%c", b[i], i == n ? '\n' : ' ');

    return 0;
}
posted @ 2020-05-10 14:39  nonameless  阅读(81)  评论(0编辑  收藏  举报