2017.7.11 暑期第一次模拟赛(By Geng4512 大犇) Day 1 小结

关于考试:

好久没考试,都快没考试的感觉了…
终于在今天开始了愉快的暴0生活…
今天刚开始考,发现第1题是贪心水题之后,整个人就放松了,然而结果却是水题WA掉了…(考后有种欲哭无泪的感觉,以后看到水题还是要认真码啊…)
接着就是第二题,看完题解后感觉很简单,但是关于树的知识点都忘得差不多了,本来一道重心水题,结果我还是找规律+归纳法+…一阵乱搞,最后才找出来…(看来以后还是要多复习一下以前的知识…“好像现在连并查集、平衡树、STL都忘得差不多了…”)
最后就是第三题,看半天都没怎么找到思路,数论和组合数学太差,算法都想出来了,但却止步于和组合数学结合来降低时间复杂度…

下面附上题和代码


Task 1: 隔膜

问题描述:

steam夏季大促销来啦,azui大爷最近在steam上买了1mol的游戏。一天他突然发现了一个搬砖的游戏: 有N种砖头,每种砖头有mi个,每一个的价值为di。每一个单位时间你必须搬一块砖,到无砖可搬为止。有一个得分系数F,初始时为1。搬一块砖的得分为当时的得分系数F*di。有T个时间分割点。每过一个时间分割点,F会自己加1。 例如在时间pi的得分为i*di,而在时间pi+1的得分为(i+1)*di。
azui大爷觉得这个游戏too simple,不想去玩,于是让你去帮他上分,希望你能告诉他每局游戏的最大的分。
你一定知道这么简单的题目怎么做,快帮帮azui大爷吧。

输入:

第一行一个数N。
接下来的N行,每行两个数,表示mi和di。
之后的一行一个数T。
接下来的一行T个数,pi。

输出:

一个数,表示最大得分

样例输入:

1
1 1000
1
1

样例输出:

1000

数据范围:

对于30%的数据,1<=N<=10
对于100%的数据,
1≤N≤100
1≤mi≤109
0≤di≤1000
1≤t≤100
1≤p2<p3<…≤pt1012

解题思路:

我们可以先假设有两种不同的砖的权值分别为a,b(a<b),可以在两个不同时间段搬,时间段所对应的权值分别为c,d(c<d)。
那么我们此时可以得到两种不同搬运方式的最大分数分别是:①a*c+b*d, ②a*d+b*c。
通过做差(①-②)我们可得:(b-a)*(d-c),又∵b<t;a,d<t;c ∴原式>0 即:①>②。
因为时间的权值随着时间的递增而递增,所以要是分数最大,那么应该让砖的权值小的在前面。这样就是一道简单的贪心了。

Code:

#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long LL;
const int MAXN = 105;

int n, t;
LL ans, p;

struct Node{
    int m, d;
    bool operator < (const Node &x) const{
        return d < x.d;
    }
}arr[MAXN];

int main(){
    scanf("%d",&n);
    for(int i = 1; i <= n; ++ i)
        scanf("%d%d",&arr[i].m,&arr[i].d);
    sort(arr+1, arr+n+1);
    scanf("%d",&t);
    LL lst = 0, used = 0; int pos = 1;
    for(int i = 1; i <= t; ++ i){
        scanf("%lld",&p);
        LL tmp = p;
        p -= lst, lst = tmp;
        while(pos <= n && p){
            if(p >= (arr[pos].m-used)){
                ans += 1ll*i*(arr[pos].m-used)*(arr[pos].d);
                p -= (arr[pos].m-used), used = 0, pos ++;
            }
            else{
                ans += 1ll*i*p*arr[pos].d;
                used += p, p = 0;
            }
        }
    }
    if(used) ans += 1ll*(t+1)*(arr[pos].m-used)*arr[pos].d, pos ++;
    while(pos <= n){
        ans += 1ll*(t+1)*arr[pos].m*arr[pos].d;
        pos ++;
    }
    printf("%lld",ans);
    return 0;
}

Task 2: 快递配对

问题描述:

azui大爷厌倦了每天在家颓废的生活,于是开始打工送快递。Jeremy同学不想让azui大爷太轻松,于是想让他送快递的路程尽可能的长。
一句话来说就是:
给出一棵n个点的树,将这n个点两两配对,求所有可行的方案中配对两点间的距离的总和最大为多少。

输入:

一个数n(1<=n<=100,000,n保证为偶数)
接下来n-1行每行三个数x,y,z表示有一条长度为z的边连接x和y(0<=z<=1,000,000,000)

输出:

一个数表示最大的距离总和。

样例输入:

6
1 2 1
1 3 1
1 4 1
3 5 1
4 6 1

样例输出:

7

数据范围:

对于20%的数据,n <= 14
对于另20%的数据,所给的树是一条链。
对于100%的数据,n <= 100000

解题思路:

【概念补充】树的重心:树的重心也叫树的质心。找到一个点,其所有的子树中最大的子树节点数最少,那么这个点就是这棵树的重心,删去重心后,生成的多棵树尽可能平衡。

首先,有这样一个结论:如果要使距离总和最大,那么任意两个匹配点的路径一定经过树的重心。

证明:
假设当前我们有至少一组匹配的路径没有经过重心。
这里我们分开讨论路径未经过重心的匹配组数为奇和偶时的情况:
Case 1:如果组数为偶数,那么,显而易见的,这些匹配会独立分散在以重心为根节点的子树当中。那么,我们就可以将这些匹配先拆开,然后分别与其他子树中拆开的节点相连,因为开始时这些匹配都未经过重心,那么就会比原来多出“每一个拆开节点到中心的距离减去原先这些匹配的距离和”的贡献。因为拆开的匹配开始都存在于重心的子树中,所以对于任意匹配而言,其的距离一定包含于其到重心的距离和,即:dis(i,j) <= dis(i,rt)+dis(j,rt)。
所以,证得当组数为偶数时,结论成立。
Case 2:如果组数为奇数,大致与偶数是相同,这里就不再赘述了。

证完之后,我们就可以直接找树的重心了,如果忘了怎么找重心,可以看一下,下面这段代码。

int pos, cmp;
bool vis[MAXN];
int dfs_find(int u){
    vis[u] = 1;
    int siz = 1, Max = 0, tmp;
    for(int i = fir[u]; i; i = Edges[i].nxt){
        if(vis[Edges[i].e]) continue;
        tmp = dfs_find(Edges[i].e);
        if(tmp > Max) Max = tmp;
        siz += tmp;
    }
    if(n-siz > Max) Max = n-siz;
    if(Max < cmp) pos = u, cmp = Max;
    return siz;
}

下面是整道题的代码

Code:

#include <cstdio>
#include <cstring>

const int MAXN = 100005;

int n;
inline void Read(int &Ret){
    char ch;
    while(ch = getchar(), ch < '0' || ch > '9');
    Ret = ch - '0';
    while(ch = getchar(), ch >= '0' && ch <= '9')
        Ret = Ret * 10 + ch - '0';
}

int fir[MAXN], ecnt = 1;
struct Node{int e, v, nxt;}Edges[MAXN<<1];
inline void AddEdge(int u, int e, int v){
    Edges[++ecnt].e = e;
    Edges[ecnt].v = v;
    Edges[ecnt].nxt = fir[u];
    fir[u] = ecnt;

    Edges[++ecnt].e = u;
    Edges[ecnt].v = v;
    Edges[ecnt].nxt = fir[e];
    fir[e] = ecnt;
}

int pos, cmp;
bool vis[MAXN];
int dfs_find(int u){
    vis[u] = 1;
    int siz = 1, Max = 0, tmp;
    for(int i = fir[u]; i; i = Edges[i].nxt){
        if(vis[Edges[i].e]) continue;
        tmp = dfs_find(Edges[i].e);
        if(tmp > Max) Max = tmp;
        siz += tmp;
    }
    if(n-siz > Max) Max = n-siz;
    if(Max < cmp) pos = u, cmp = Max;
    return siz;
}

long long ans;
void dfs_calc(int u, long long dis){
    vis[u] = 1, ans += dis;
    for(int i = fir[u]; i; i = Edges[i].nxt){
        if(vis[Edges[i].e]) continue;
        dfs_calc(Edges[i].e, dis+Edges[i].v);
    }
}

int main(){
    Read(n);
    int a, b, c;
    for(int i = 1; i < n; ++ i){
        Read(a),Read(b),Read(c);
        AddEdge(a, b, c);
    }
    pos = 1, cmp = n;
    dfs_find(pos);
    memset(vis, 0, sizeof vis);
    dfs_calc(pos, 0);
    printf("%lld\n",ans);
    return 0;
}

Task 3:子集

问题描述:

azui大爷在quack大爷的带领下开始玩集合了,可是他太懒了,不想做quack大爷布置的作业题,便拿来给你做了:S 集合中有n个不同的元素,我们从1-n标号。考虑S 的子集Si,j,将这些子集排成一个r行c列矩阵的样子。其中第一行为S1,1,S1,2,…,S1,c,第二行为S2,1,S2,2,..,S2,c一直到第r行为Sr,1, Sr,2,…, Sr,c。这些集合还满足对于在一行中左右相邻的两个集右,左侧是右侧的子集,即Si,j∈Si,j+1。这些集合还满足对于在一列中上下相邻的两个集合,上方是下方的子集,即Si,j∈Si+1,j。问对于S 的全部子集,有多少可能的情况排成上述的矩阵(每个子集可以重复使用),结果模109 +7 输出。你一定知道这么简单的题目怎么做,快帮帮azui大爷吧。

输入:

一行三个数n,r,c.

输出:

一个数表示答案。

样例输入:

1 2 2

样例输出:

6

样例解释:

12
34

如上图标号后,有:1是2和3的子集,1,2,3都是4的子集。
用0表示空集,1表示元素1。
有以下6种情况:
00 | 00 | 00 | 01 | 01 | 11
00 | 01 | 11 | 01 | 11 | 11

数据范围:

对于10%的数据,r*c*n <= 16
对于20%的数据,r*c <= 16, n <= 100
对于另20%的数据,r=1, c<=1000000
对于另10%的数据,n=1
对于100%的数据,r, c <= 1000000, n <= 10^9

解题思路:

乍一看去,这道题好像毫无思路,但我们可以简化条件。
在简化条件时,我们首先可以发现,这里无论有多少种不同的元素,但其对于答案的贡献都是不会相互影响的。那么根据乘法原理,我们就可以知道最后的答案就是先计算出当n=1时的方案数,然后有k种元素,就k次方就行了。
那么,我们就重点看一下当n=1时的方案数。
如果我们把任一,一种方案先用0、1在一个矩阵中表示出来,我们就不发现:如果,在第i行第j列放上一个0的话,那么为了保证方案合法,就必须保证在以(1,1)为左上角,(i,j)为右下角的矩形中不存在1。这时,我们就能通过表格直观的发现,整张表格实际上被分为了在左上的0,和右下的1两个部分。 

这里写图片描述

而中间分割0和1的折线的方案数就是n=1时的答案,根据组合数学,我们可以知道这里的方案就是C(n+r, c)。

Code:

#include<cstdio> 

const int MOD=1e9+7; 

inline int ksm(int a,int b){ 
    long long ret=1,tmp=a; 
    while(b){ 
        if(b&1) ret=ret*tmp%MOD; 
        tmp=tmp*tmp%MOD;b>>=1; 
    } 
    return ret%MOD; 
} 

inline int fac(int n){ 
    int ret=1; 
    for(int i=2;i<=n;i++) 
        ret=(long long)ret*i%MOD; 
    return ret; 
} 

int inv(int x){ 
    if(x==1) return 1; 
    return (long long)(MOD-MOD/x)*inv(MOD%x)%MOD; 
} 

int C(int n,int m){ 
    return (long long)fac(n)*inv(fac(m))%MOD*inv(fac(n-m))%MOD; 
} 

int main() { 
    int n,r,c; 
    scanf("%d%d%d",&n,&r,&c); 
    printf("%d",ksm(C(r+c,r),n));
    return 0; 
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值