2020 年第一届辽宁省大学生程序设计竞赛(A,B,C,E,F,G,H,I,J,K)

oj: 牛客

A.组队分配(签到题)

oj: 牛客

题解

排序后直接输出。

代码

#pragma GCC optimize(2)
#include 
#define _for(i, a) for(int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for(int i = (a), lennn = (b); i <= lennn; ++i)
using namespace std;

inline int read() {
    int x(0), f(1); char ch(getchar());
    while (ch<'0' || ch>'9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0'&&ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return x * f;
}

int n;
vector< pair<int, string> > a;

void init() {
    a.resize(n * 3);
}

void sol() {
    init();
    char s[21];
    int x;
    _for(i, n * 3) cin >> a[i].second >> a[i].first;
    sort(a.begin(), a.end());
    _for(i, n) {
        printf("ACM-%d", i);
        vector<string> t;
        for(int j = 0; j < 3; ++j) t.push_back(a[i * 3 + j].second);
        for(int j = 2; j >= 0; --j) cout << " " << t[j];
        cout << "\n";
    }
}

int main() {
    int T = read();
    _for(i, T) {
        n = read();
        sol();
    }
    return 0;
}

B.两点距离(找规律)

oj: 牛客

待补

C.轮到谁了?(找规律)

oj: 牛客

题解

观察后发现是斐波那契数列,直接暴力递推,中间对 m m m 取模。不要忘记最后先加上一个 m m m 再对 m m m 取模,否则会出现负数。

代码

#pragma GCC optimize(2)
#include 
#define _for(i, a) for(int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for(int i = (a), lennn = (b); i <= lennn; ++i)
using namespace std;
typedef long long LL;

inline int read() {
    int x(0), f(1); char ch(getchar());
    while (ch<'0' || ch>'9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0'&&ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return x * f;
}

int main() {
    int T = read();
    _for(i, T) {
        LL n = read(), m = read();
        LL a = 1, b = 0, c = 1;
        _for(j, n) {
            c = (a + b) % m;
            a = b, b = c;
        }
        printf("%lld\n", (c - 1 + m) % m);
    }
    return 0;
}

E.线段树

oj: 牛客

待补

最长回文串(找规律)

oj: 牛客

题解

只要两个字符串中的每一个字符的出现次数都一样,我们就认为这两个字符串是一样的。

只要一个串的出现次数t大于1,那么这个串就一定能作为回文串的两端中的某一段,并且出现偶数次。对答案的贡献是 2 m ⌊ t 2 ⌋ 2m\lfloor \frac{t}{2}\rfloor 2m2t

我们在出现次数为1的串里找一个这样的串: 串内出现次数为奇数的字符 的个数不超过1。这样我们就能把这个串作为回文中心。对答案的贡献是 m m m

代码

#pragma GCC optimize(2)
#include 
#define _for(i, a) for(int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for(int i = (a), lennn = (b); i <= lennn; ++i)
using namespace std;

inline int read() {
    int x(0), f(1); char ch(getchar());
    while (ch<'0' || ch>'9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0'&&ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return x * f;
}

map< string, int > mp;
int n, m;

void init() {
    mp.clear();
}

int che(string s) {
    map<char, int> tem;
    for(char c : s) ++tem[c];
    int cnt = 0;
    for(auto i : tem) {
        cnt += (i.second & 1);
    }
    return cnt <= 1;
}

void sol() {
    init();
    string s;
    _for(i, n) {
        cin >> s;
        sort(s.begin(), s.end());
        ++mp[s];
    }
    int ans = 0, f = 0;
    for(auto i : mp) {
        if(i.second > 1) ans += i.second / 2 * 2 * m;
        else if(f == 0) {
            if(che(i.first)) {
                ans += m;
                f = 1;
            }
        }
    }
    printf("%d\n", ans);
}

int main() {
    n = read(), m = read();
    sol();
    return 0;
}

G.管管的幸运数字(素数)

oj: 牛客

题解

由于询问的n的值不超过 10000 10000 10000,所以我们筛出 15000 15000 15000 以内的素数就足够了。

先判断 n n n 是不是素数,这一步可以合并到寻找不小于 n n n 的最小的素数中。

分别找出不小于 n n n 的最小的素数和比 n n n 小的最大的素数。

不小于 n n n 的最小的素数是 *lower_bound(arr.begin(), arr.end(), d)。判断值是否与 n n n 相等。

n n n 小的最大的素数是 *upper_bound(arr.begin(), arr.end(), d)

如果 n n n 不是素数,则二者与 n n n 的距离取最小值。

代码

#include 
#define _for(i, a) for(int i = 0, lennn = (a); i < lennn; ++i)
#define _rep(i, a, b) for(int i = (a), lennn = (b); i <= lennn; ++i)
using namespace std;

inline int read() {
    int x(0), f(1); char ch(getchar());
    while (ch<'0' || ch>'9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0'&&ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return x * f;
}

vector<int> arr;
int vis[1000006];
void doit(int maxnum) {
    for(int i = 2; i <= maxnum; ++i) {
        if(!vis[i]) arr.push_back(i);
        for(int j = 0; j < arr.size() && arr[j] * i <= maxnum; ++j) {
            vis[arr[j] * i] = 1;
            if(i % arr[j] == 0) break;
        }
    }
}

int main() {
    doit(15000);
    int T = read();
    _for(i, T) {
        int d = read();
        int p = lower_bound(arr.begin(), arr.end(), d) - arr.begin();
        if(d == arr[p]) printf("YES\n");
        else {
            int pp = upper_bound(arr.begin(), arr.end(), d) - arr.begin() - 1;
            printf("%d\n", min(d - arr[pp], arr[p] - d));
        }
    }
    return 0;
}

H.鸽子的浮点运算(模拟)

oj: 牛客

代码

#include 
using namespace std;

inline int read() {
    int x(0), f(1); char ch(getchar());
    while (ch<'0' || ch>'9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0'&&ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return x * f;
}

struct poi {
    int s;
    vector<int> e, m;
    poi(){}
    poi(double x) {
        e.clear(); m.clear();
        s = (x <= 0); x = fabs(x);
        int cnt = 15;
        while(x >= 2) {
            ++cnt;
            x /= 2;
        }
        while(x < 1) {
            --cnt;
            x *= 2;
        }
        for(int i = 0; i < 5; ++i) {
            e.push_back(cnt & 1);
            cnt >>= 1;
        }
        reverse(e.begin(), e.end());
        x -= (int)x;
        for(int i = 0; i < 10; ++i) {
            x *= 2;
            m.push_back((int)x);
            x -= (int)x;
        }
    }
    double getval() {
        double ans = 1, base = 1;
        for(int i = 0; i < 10; ++i) {
            base /= 2;
            if(m[i]) ans += base;
        }
        int q = 0, bas = 1;
        for(int i = 4; i >= 0; --i) {
            if(e[i]) q += bas;
            bas <<= 1;
        }
        q -= 15;
        while(q > 0) ans *= 2, --q;
        while(q < 0) ans /= 2, ++q;
        return ans * (s ? -1 : 1);
    }
    void print() {
        printf("%d", s);
        for(int i : e) printf("%d", i);
        for(int i : m) printf("%d", i);
        printf("\n");
    }
};

int main() {
    int T = read();
    for(int i = 0; i < T; ++i) {
        int op = read(); double x1, x2;
        scanf("%lf%lf", &x1, &x2);
        poi p1 = poi(x1), p2 = poi(x2);
        x1 = p1.getval(), x2 = p2.getval();
        double ans = x1;
        if(op == 2) ans = x1 + x2;
        else if(op == 3) ans = x1 * x2;
        poi pa = poi(ans);
        pa.print();
    }
    return 0;
}

I.鸽子的整数运算(签到题)

oj: 牛客

待补

J.鸽者文明的三体问题(计算几何)

oj: 牛客

待补

K.xor(动态规划)

oj: 牛客

题解

再回顾一下题意:将数组 a a a 分成若干彼此互不相交的组,每组的异或和为 x x x。求分组的划分数。

思考一个问题:任何异或和为 x x x 的区间都能自成一组吗?

显然不是,例如当 x x x 1 1 1 时的2 3 2 3中的3 2,虽然异或和为 1 1 1,但如果把3 2分为一组,两边的23就无处安放了。正确的分法应该是2 32 3

那应该怎么分呢?

考虑将 a a a 从左到右贪心地分成若干个足够小的异或和为x的组,数组内每一个元素都在一个这样的组内。例如当 x x x 1 1 1 时, a a a1 2 2 1 2 2 1时,我们将数组分为1, 2 2 1, 2 2 1

又发现分出的组还可以再分出一些足够小的异或和为0的组,将 a a a 进一步划分为:1, 2 2, 1, 2 2, 1。再例如1 2 2 2 2 1分为1, 2 2, 2 2, 1。之所以划分出异或和为 0 0 0 的组,是因为当他们出现在两个异或和为 1 1 1 的组之间时,它们既可以合并到左边的组,也可以合并到右边的组,所以他们的数量值得我们关注。

为了方便下文叙述,我们将 异或和为 x x x 的组 称为 u x ux ux,将 异或和为 0 0 0 的组 称为 u 0 u0 u0,将 把数组 a a a 分成若干彼此互不相交的组且每组的异或和为 x x x 的划分数 称为 划分数

这样分完之后,所有的合理的对数组 a a a 的划分都是 u x ux ux u 0 u0 u0 组合的结果。而且每次必须选奇数个 u x ux ux 合并到一起,不然每个 u x ux ux 的值为 x x x,偶数个 x x x 异或之后为 0 0 0,而不是 x x x

为了方便进行状态转移,我们在 u x ux ux 中间插入 u 0 u0 u0 的数量,如果不存在 u 0 u0 u0 就插入 0 0 0。我们把这个数组叫做数组 b b b。例如将1 2 2 2 2 1表示为ux 2 ux,将1 2 2 1 2 2 1 1表示为ux 1 ux 1 ux 0 ux。对于数组两端的 u 0 u0 u0,我们是不考虑的。因为它们只能被合并到相邻的 u x ux ux 而不能单独存在,对答案没有贡献,所以就当它们没有出现过就行了。

然后定义 d p [ i ] dp[i] dp[i] b [ i : ] b[i:] b[i:](从 i i i 到末尾)的划分数。且只考虑 i i i 为偶数的情况( i i i 0 0 0 开始计数)之所以不考虑 i i i 为奇数的情况是因为奇数位置对应的都是 u 0 u0 u0 的个数,上面也说了以 u 0 u0 u0 作为数组边界时时可以将其视作不存在,所以 d p [ 偶 数 ] dp[偶数] dp[] 就和 d p [ 偶 数 + 1 ] dp[偶数+1] dp[+1] 一样了,没必要重复计算。

边界:

  1. 考虑 b b b 数组最后一个 u x ux ux,答案时 1 1 1 d p [ − 1 ] = 1 dp[-1] = 1 dp[1]=1(-1代表最后一个元素,后面以此类推)。
  2. 考虑 b b b 数组最后两个 u x ux ux,不能执行合并操作。中间有 d p [ − 2 ] dp[-2] dp[2] u 0 u0 u0,可以划分 d p [ − 2 ] dp[-2] dp[2] u 0 u0 u0 到左边,其他的划分到右边,也可以划分 d p [ − 2 ] − 1 dp[-2]-1 dp[2]1 个到左边,也可以划分 d p [ − 2 ] − 2 dp[-2]-2 dp[2]2 个…,划分法就有 d p [ − 2 ] + 1 dp[-2]+1 dp[2]+1 种。

转移:

  1. i i i 个状态有两种转移方式,其一是将 b [ i ] b[i] b[i] b [ i + 2 ] b[i+2] b[i+2] 合并,但是由于每次合并必须是奇数个 u x ux ux,所以将 b [ i ] b[i] b[i] 合并到 b [ i + 2 ] b[i+2] b[i+2] 后的组合数实际上是由 b [ i + 4 ] b[i+4] b[i+4] 决定的。可以认为把 b [ i ] b[i] b[i], b [ i + 2 ] b[i+2] b[i+2], b [ i + 4 ] b[i+4] b[i+4] 绑定到了一起(中间的 u 0 u0 u0 也是绑在一起),这样就可以把他们仨看成只有一个 b [ i + 4 ] b[i+4] b[i+4]。所以此时 d p [ i ] + = d p [ i + 4 ] dp[i]+=dp[i+4] dp[i]+=dp[i+4]
  2. 其二是将 b [ i ] b[i] b[i] 单独作为一个组(可能前面会有别的组选择和 b [ i ] b[i] b[i] 合在一起,但此时不考虑前面的),这时 b [ i ] b[i] b[i] b [ i + 2 ] b[i+2] b[i+2] 中间有 b [ i + 1 ] b[i+1] b[i+1] u 0 u0 u0,一共有 b [ i + 1 ] + 1 b[i+1]+1 b[i+1]+1 种划分方式(参考边界.2)。每种又对应 d p [ i + 2 ] dp[i+2] dp[i+2] 种,所以 d p [ i ] + = ( d p [ i + 1 ] + 1 ) ∗ d p [ i + 2 ] dp[i] += (dp[i + 1] + 1) * dp[i + 2] dp[i]+=(dp[i+1]+1)dp[i+2]

综上, d p [ i ] = d p [ i + 4 ] + ( d p [ i + 1 ] + 1 ) ∗ d p [ i + 2 ] dp[i] = dp[i + 4] + (dp[i + 1] + 1) * dp[i + 2] dp[i]=dp[i+4]+(dp[i+1]+1)dp[i+2]

代码

#include 
#define rp(i, s, t) for (int i = (s); i <= (t); i++)
#define RP(i, t, s) for (int i = (t); i >= (s); i--)
#define ll long long
using namespace std;
inline int read() {
    int s = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        s = s * 10 + ch - '0';
        ch = getchar();
    }
    return s * f;
}
const int N = 1e6 + 7;
const ll mod = 1e9 + 7;
int a[N];
int n;
vector<ll> dp;
void solve() {
    dp.clear();
    n = read();
    ll x = read();
    rp(i, 1, n) a[i] = read();
    int num = 0;
    ll tem = 0;
    int nu0 = 0;
    rp(i, 1, n) {
        tem ^= a[i];
        if(tem == 0) ++nu0;
        else if (tem == x) {
            if(num == 0) dp.push_back(0);
            else {
                dp.push_back(nu0);
                dp.push_back(0);
            }
            nu0 = 0;
            tem = 0;
            ++num;
        }
    }
    if(tem || dp.size() == 0) {
        printf("0\n");
        return;
    }
    if(dp.size() == 1) {
        printf("1\n");
        return;
    }
    dp[dp.size() - 1] = 1;
    dp[dp.size() - 3] = dp[dp.size() - 2] + 1;
    for(int i = dp.size() - 5; i >= 0; i -= 2) {
        dp[i] = dp[i + 4] + (dp[i + 1] + 1) * dp[i + 2];
        dp[i] %= mod;
    }
    printf("%lld\n", dp[0]);
}
int main() {
    solve();
    return 0;
}

总结

交题前多测几发真的不浪费时间!!!

交题前多测几发真的不浪费时间!!!

交题前多测几发真的不浪费时间!!!

说不定胡乱写组数据就会发现自己不但过不了,甚至解法都不对。。

K题漫长的debug过程中全靠胡乱造数据发现bug,如果我没有造这么多数据,可能到比赛结束我还在死扣一个错误的解法。

你可能感兴趣的:(#,思维,找规律,#,动态规划,★,ACM,★)