Codeforces Round #558 (Div. 2)题解

Codeforces Round #558 (Div. 2)题解

A. Eating Soup

水题,直接给出代码。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
int n, m;
int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n >> m;
    int remain = n - m;
    if(remain <= n / 2) {
        cout << remain;
        return 0;
    }
    cout << max(n - remain, 1);
    return 0;
}

B2. Cat Party (Hard Edition)

问题最后只和每个数出现的次数之间的大小有关,有如下几种情况:

  • 假设出现次数最多为\(x\)次,那么一个数的出现次数为\(x\)次,其它数的出现次数为\(x-1\)次。
  • 假设出现次数最多为\(x\)次,那么有一个数的出现次数为\(1\),其余数的出现次数都为\(x\)次。

这还是很好想的,只有这两种情况与题意相符合。所以我们用一个数组维护出现次数,另一个数组维护出现次数的次数,再维护一下出现最多的次数为多少,最后扫一遍判断就好了。
代码如下:(写的时候我傻逼离散化了一下,其实可以不用,这样\(O(n)\)就可以解决)

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
int n;
int sum[N], a[N], b[N], cnt[N];

int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i], b[i] = a[i];
    sort(b + 1, b + n + 1);
    int D = unique(b + 1, b + n + 1) - b - 1;
    for(int i = 1; i <= n; i++) a[i] = lower_bound(b + 1, b + D +1, a[i]) - b;
    int ans = 1;
    int num = 0;
    int mx = 0, num1;
    for(int i = 1; i <= n; i++) {
        if(sum[a[i]] > 0) cnt[sum[a[i]]]--;
        sum[a[i]]++;
        cnt[sum[a[i]]]++;
        if(sum[a[i]] > mx) {
            mx = sum[a[i]];
            num1 = 1;
        } else if(sum[a[i]] == mx) {
            num1++;
        }
        if(num1 == 1 && cnt[mx - 1] * (mx - 1) == i - mx) ans = i;
        if(num1 * mx == i - 1 && cnt[1] == 1) ans = i;
        if(mx == 1) ans = i;
    }
    cout << ans;

    return 0;
}

C2. Power Transmission (Hard Edition)

当时比赛的时候被这个题关了,有点可惜唉。
已知两个点\(A(x_1,y_1),B(x_2,y_2)\),那么经过这两个点的直线方程为:\(y=\frac{y_2-y_1}{x_2-x_1}x+y_1-x_1*\frac{y_2-y_1}{x_2-x_1}\),可以化为:\((x_2-x_1)*y-(y_2-y_1)*x=y_1*x_2-y_2*x_1\)
之后我们把形式统一一下,暴力搞就行了。
代码如下:

Code
#include <bits/stdc++.h>
#define MP make_pair
using namespace std;
typedef long long ll;
const int N = 2505;
int n;
struct node{
    int x, y;
    bool operator < (const node &A)const{
        if(A.x == x) return y < A.y;
        return x < A.x;
    }
}a[N], b[N];
map <pair<int, int>, set<int> > mp;
map <int, int> c1, c2 ;
int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i++) {
        cin >> a[i].x >> a[i].y;
    }
    ll ans, tot = 0;
    ans = 0;
    int num = 0;
    for(int i = 1; i <= n; i++) {
        for(int j = i + 1; j <= n; j++) {
            int dx = a[j].x - a[i].x ;
            int dy = a[j].y - a[i].y ;
            int g = __gcd(dx, dy) ;
            if(dx == 0 && dy == 0) continue ;
            dx /= g ;
            dy /= g ;
            if(dy < 0 || (dy == 0 && dx < 0)) {
                dx = -dx ;
                dy = -dy;
            }
            int c = dy * a[i].x - dx * a[i].y ;
            if(!mp[MP(dy, dx)].count(c)) {
                ++tot;
                mp[MP(dy, dx)].insert(c) ;
                ans += tot - mp[MP(dy, dx)].size() ;
            }
        }
    }
    cout << ans ;
    return 0;
}

D. Mysterious Code

比较套路的一个KMP+DP,因为长度很小,所以先暴力处理出\(f(i,j)\)表示当前在第\(i\)个位置,后面一位为\(j(0<j<26)\)时,能匹配到的最大位置。这里的匹配是自己和自己匹配,类似于KMP算法中的next数组。
之后直接由\(dp(i,j,k)\)表示第一个串在\(i\)位置,第二个串在第\(j\)个位置,第三个串在第\(k\)个位置,所求的最大值。
之后直接转移就行了。至于这里的转移为什么是正确的,可以类比于next指针想一下。
代码如下:

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1005, M = 55, MAX = 26;
const int INF = 1e9;
int nxts[M][MAX], nxtt[M][MAX] ;
char s[M], t[M], c[N];
int tests[N] ;
int dp[N][M][M] ;
void Get_Nxts(char *s, int Nxt[][MAX]) {
    int L = strlen(s + 1) ;
    for(int i = 0; i <= L; i++) {
        for(int p = 0; p < MAX; p++) {
            char nxt = p + 'a' ;
            for(int j = min(i + 1, L); j >= 0; j--) {
                bool flag = true ;
                for(int k = 1; k < j; k++) {
                    if(s[k] != s[i + 1 - j + k]) flag = false ;
                }
                if(s[j] != nxt) flag = false;
                if(flag) {
                    Nxt[i][p] = j;
                    break ;
                }
            }
        }
    }
}
void getmx(int &x, int y) {
    if(y > x) x = y;
}
int main() {
    scanf("%s",c + 1);
    scanf("%s%s",s + 1, t + 1);
    Get_Nxts(s, nxts) ;
    Get_Nxts(t, nxtt) ;
    for(int i = 0; i < N; i++)
        for(int j = 0; j < M; j++)
            for(int k = 0; k < M; k++)
                dp[i][j][k] = -INF;
    dp[0][0][0] = 0;
    int L1 = strlen(c + 1), L2 = strlen(s + 1), L3 = strlen(t + 1);
    for(int i = 0; i < L1; i++) {
        for(int j = 0; j <= L2; j++) {
            for(int k = 0; k <= L3; k++) {
                if(dp[i][j][k] == -INF) continue ;
                if(c[i + 1] == '*') {
                    for(int p = 0; p < MAX; p++) {
                        int d = 0;
                        if(nxts[j][p] == L2) d++;
                        if(nxtt[k][p] == L3) d--;
                        getmx(dp[i + 1][nxts[j][p]][nxtt[k][p]], dp[i][j][k] + d) ;
                    }
                } else {
                    int d = 0;
                    int p = c[i + 1] - 'a' ;
                    if(nxts[j][p] == L2) d++;
                    if(nxtt[k][p] == L3) d--;
                    getmx(dp[i + 1][nxts[j][p]][nxtt[k][p]], dp[i][j][k] + d) ;
                }
            }
        }
    }
    int ans = -INF;
    for(int i = 0; i <= L2; i++)
        for(int j = 0; j <= L3; j++)
            getmx(ans, dp[L1][i][j]) ;
    cout << ans;
    return 0;
}

E. Magical Permutation

这是一个很巧妙的构造题orz。
先简单说下题意吧:就是会给一个集合\(S\),其中包含\(S_1,S_2,\cdots,S_n\)\(n\)个数。现在要求你找出最大的\(x\),构造出一个\(0\)\(2^x-1\)中的所有数的排列。并且相邻两个数的异或值在这个集合\(S\)中。
首先我们可以对这个题进行一些分析:\(0\)左右的数一定在\(S\)中,假设其中一个数为\(x\),那么在\(x\)另一旁的一个数设为\(y\),因为\(x\)^\(y\in S\),可知\(y\)\(S\)中某些数的异或值。以此类推,就有我们所有构造出来的数都要为\(S\)中某些数的异或值。
到了这里想到了些什么?每个二进制位都存在的线性基!
所以最大的\(x\)我们就可以直接通过线性基来构造。之后解决的问题就是具体的方案了。
这里构造方案需要一点Gray code(格雷码)的知识,但我们先看看具体构造方案:

void gray_code(int x) {
    cnt = 1;
    cout << x << '\n' ;
    for(int i = 1; i <= x; i++)
        for(int j = cnt; j > 0; j--) ans[++cnt] = ans[j] ^ now[i] ;
    for(int i = 1; i <= cnt; i++) cout << ans[i] << ' ' ;
}

这里就短短两行代码,却十分精妙。看了我好半天
这里一开始有1个数,后面有2个,再后面有4个...以此类推。
每一次都是将前面的数翻转过来异或当前二进制位上面线性基的值。
下面来证明一下这样做的正确性:
当数个数比较少的时候,显然满足条件;
我们假设前面\(2^y\)个数满足条件,很容易知道第\(2^y\)个数一定是集合\(S\)中的某个值,这个很容易知道,因为最后是和0异或的(可以看看之后代码中now数组中存的值)。那么现在来进行构造,可以知道第\(2^y\)个数和第\(2^{y+1}\)异或的值也一定是在\(S\)中的某个数。并且后面\(2^y\)个数两两之间的异或值也一定在集合\(S\)中,因为这是由前面构造过来的,多异或了一个now,两两异或这个多异或的就会被抵消,就相当于前面的两个异或,由我们的假设就得证了。

构造方法和证明说完了,那这和格雷码有什么关系呢。

格雷码的构造方式可以分治来构造,就是将一个二进制串前面加一个0,然后翻转过来在前面加一个1。(详见我上面给出的链接中的镜射排列部分)
注意到这个翻转和我们构造时的翻转很像,那么格雷码有什么性质呢?相邻两个数二进制只差一位!
那么我们将x个线性基的值每一个都看作二进制中的1,那么我们通过类似方法构造出来的也是每个相邻的都差一个二进制1,也不就是一个属于\(S\)集合中的值吗??
这样,格雷码和我们的构造方式就联系了起来!是不是感觉十分巧妙,也不负这个题目中的"Magic"了。

代码如下:

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 5;
int n, cnt;
int a[N], p[22], now[22], ans[N];
void ins(int x) {
    int tmp = x;
    for(int i = 20; i >= 0; i--) {
        if((x >> i) & 1) {
            if(!p[i]) {
                p[i] = x;
                now[++cnt] = tmp;
                break ;
            } else x ^= p[i] ;
        }
    }
}
void gray_code(int x) {
    cnt = 1;
    cout << x << '\n' ;
    for(int i = 1; i <= x; i++)
        for(int j = cnt; j > 0; j--) ans[++cnt] = ans[j] ^ now[i] ;
    for(int i = 1; i <= cnt; i++) cout << ans[i] << ' ' ;
}
int main() {
    ios::sync_with_stdio(false); cin.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i];
    for(int i = 20; i >= 0; i--) {
        memset(p, 0, sizeof(p)); cnt = 0 ;
        for(int j = 1; j <= n; j++) {
            if(a[j] < 1 << i) ins(a[j]) ;
        }
        if(i == cnt) break ;
    }
    gray_code(cnt) ;
    return 0 ;
}
posted @ 2019-05-16 19:12  heyuhhh  阅读(362)  评论(0编辑  收藏  举报