2018上海高校金马五校赛训练日志

solve  5(A  E  F  I  L)

rank  77

水题总体没有很卡,但提升的题都没有思路,实力差距还是有的。

个人感觉出了的题都是铜牌及以下的难度。

Wasserstein Distance

<qj>

思路:

已知两堆的值,我求出它们的差,如果是正数,说明原始堆要挪走一部分;否则,则要从别的堆移一部分过来。

不难证明,为了使花费最小,直接从左到右扫一遍便可,这样一定最优。

两个优先队列,存下标和差值。扫一遍就过了。

合约数

 预处理出$[2, 10000]$区间内所有数$x$的合约数,假设放在vector $vf[x]$中。这里把$1$当素数看就OK了。

然后,对树做一遍dfs。搜索过程中,统计每一个节点属性值$x$的个数。

进入某个节点$root$时,先记录好在以$root$为根的子树之外的$val_{root}$的合约数个数,再从该节点往下dfs,把搜索到$val_{root}$的合约数个数减去刚开始记录好的,就是以$root$为根的子树中,属性值是$val_{root}$的合约数的节点个数。

#include<bits/stdc++.h>
using namespace std;
#define MAXN 20010
#define MOD 1000000007LL
typedef long long LL;
typedef struct{
    int to, next;
}Edge;
Edge tree[MAXN * 2];
int head[MAXN], ecnt, n, p, val[MAXN], cnt[MAXN];
bool is[MAXN];
vector<int> vf[MAXN];
LL ans = 0;

void init(){
    memset(head, -1, sizeof(head));
    ecnt = 0;
}

void add(int from, int to){
    tree[ecnt].to = to;
    tree[ecnt].next = head[from];
    head[from] = ecnt++;
}

void init0(){
    for(int i = 0;i < MAXN;++i)vf[i].clear();
    fill(is, is + MAXN, 1);
    for(int i = 2;i < MAXN;++i){
        if(is[i]){
            for(int j = i + i;j < MAXN;j += i)is[j] = false;
        }
    }
    for(int i = 2;i < MAXN;++i){
        for(int j = 1;j * j <= i;++j){
            if(i % j == 0){
                if(!is[j])vf[i].push_back(j);
                if(j * j != i && !is[i / j])vf[i].push_back(i / j);
            }
        }
    }
}


void dfs(int root, int par){
    vector<int> tmp;
    int x = val[root];
    for(int i = 0;i < vf[x].size();++i)tmp.push_back(cnt[vf[x][i]]);
    for(int i = head[root];~i;i = tree[i].next){
        int to = tree[i].to;
        if(to != par){
            dfs(to, root);
        }
    }
    cnt[x]++;
    for(int i = 0;i < vf[x].size();++i)ans = (ans + root * LL(cnt[vf[x][i]] - tmp[i])) % MOD;
}


int main(){
//    freopen("input.txt", "r", stdin);
    int T, a, b;
    init0();
    for(scanf("%d", &T);T--;){
        init();
        scanf("%d%d", &n, &p);
        for(int i = 1;i < n;++i){
            scanf("%d%d", &a, &b);
            add(a, b), add(b, a);
        }
        for(int i = 1;i <= n;++i)scanf("%d", val + i);
        ans = 0;
        memset(cnt, 0, sizeof(cnt));
        dfs(p, -1);
        cout << ans << endl;
    }
    return 0;
}

序列变换

D  数字游戏

E  小Y吃苹果

 <qj>

 抢到了1血表示很开心。

猜个结论便可,要证明也不太难。

F  1 + 2 = 3?

G  小Y做比赛

H  小Y与多米诺骨牌

I  二数

 <qj>

思路:

对于数字s,我需要求出比s大的sa,比s小的sm

sa:

遍历串s,对于第一个奇数,把该位 +1 ,然后接下来所有位全部改成 '0' 便可。

若该位是9,需要考虑"89"这种情况,如果是9,要进位,下一位的8变成9,就需要继续进位。所以我们需要把9前面连在一起的8全都处理成0,然后在下一位添加一个2。

比如:2888889 就变成了  4000000     ,     88889123456     就变成了200000000000

sm:

遍历串s,对于第一个奇数,把该位 -1,然后接下来所有位全都改成'8'便可。

那么

如果sa - s >= s - sm    答案就是 sm

否则就是 sa

至于这个数字的大小比较,以及减法,需要手写或者套板子。

尝试过java大数,果断超时了。

贴一下模拟减法的板子

string BigInegerMinus(string s1, string s2, bool negative) // s1-s2;
{
    if (s1.size() < s2.size())
    {
        return BigInegerMinus(s2, s1, true);
    }
    if (s1.size() == s2.size())
    {
        int i = 0;
        while(i < s1.size() && s1[i] == s2[i])
            i++;
        if (s1[i] < s2[i])
        {
            return BigInegerMinus(s2, s1, true);
        }
    }
    string res(s1.size(), '0');
    int i = s1.size() -1, j = s2.size() - 1;
    int k = i;
    int borrow = 0;
    while(i >= 0 && j >= 0)
    {
        int sum = s1[i] - '0' - borrow - (s2[j] - '0');
        //cout<<sum<<endl;
        if (sum < 0)
        {
            borrow = 1;
            sum += 10;
            res[k--] = sum + '0';
        }
        else{
            borrow = 0;
            res[k--] = sum + '0';
        }
        i--;j--;
    }
    while(i >= 0)
    {
        int sum = s1[i--] - '0' - borrow;
        if (sum < 0)
        {
            borrow = 1;
            sum += 10;
            res[k--] = sum + '0';
        }
        else{
            borrow = 0;
            res[k--] = sum + '0';
        }
    }
    if (res[0] == '0')
    {
        //ignore the prefix '0's
        int index = 1;
        while(index < res.size() && res[index] == '0')
            index++;
        if (negative)
        {
            return "-" + res.substr(index, res.size() - index);
        }
        else return res.substr(index, res.size() - index);
    }
    else {
        if (negative)
        {
            return "-" + res;
        }
        else return res;
    }
}

J  小Y写文章

K  树上最大值

L  K序列

一、思路

01背包dp。设,$dp[i][j]$表示在前$i$个数中模$k$累加和为$j$的最长长度。那么,有如下递推式:

$dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - a[i]]+1)$。

需要注意的是:

  (1)$j-a[i]$可能为负;

  (2)初始状态$dp[0][0]=0$,$dp[0][i]$不存在$(0 < i < k)$。那么,$dp[i][j]$不能由不存在的状态转移过来。

  (3)因为$n*k \le 10^7$,所以,直接开辟静态数组行不通,采用二维vector动态开数组即可解决。

二、代码

#include<bits/stdc++.h>
using namespace std;
#define MAXN 100010
typedef vector<int> vec1d;
int n, k, a[MAXN];
int main(){
//    freopen("input.txt", "r", stdin);
    scanf("%d%d", &n, &k);
    for(int i = 1;i <= n;++i)scanf("%d", a + i);
    vector<vec1d> dp(n + 5, vec1d(10000010 / n + 5));
    for(int i = 0;i <= n;++i){
        for(int j = 0;j <= k;++j)dp[i][j] = -1;
    }
    dp[0][0] = 0;
    for(int i = 1;i <= n;++i){
        for(int j = 0;j < k;++j){
            int t = (j - a[i] + k) % k;
            t = (t + k) % k;
            if(dp[i - 1][t] != -1)dp[i][j] = max(dp[i][j], dp[i - 1][t] + 1);
            if(dp[i - 1][j] != -1)dp[i][j] = max(dp[i][j], dp[i - 1][j]);
        }
    }
    cout << (dp[n][0] == -1 ? 0 : dp[n][0]) << endl;
    return 0;
}
原文地址:https://www.cnblogs.com/dowhile0/p/8848589.html