2019 CCPC-Wannafly Winter Camp Day7 (Div2, onsite)(补题记录)

一篇来自ACM入门者的补题记录

赶着在周五晚上爬上来更新这篇博客,因为明天就是天梯赛了,本来计划是在天梯赛前补完秦皇岛的题的(捂脸…)计划赶不上变化啊…

这次更新的周期也比较久,因为可补的题目比较多,还是有两题没能补上…

天梯赛加油~

文章目录

    • A.迷宫
    • C.斐波那契数列
    • E.线性探查法
    • F.逆序对
    • G.抢红包机器人
    • J.强壮的排列

A.迷宫

题意:题意不是很清楚,大体上是n个点,n-1条边的图,1号点是出口,在某些点上有人,所有人一起行动,但同一个点上只能同时存在一个人,问让所有人逃离迷宫的最短时间是多少。

思路:设 t t t 为我们当前经过的时间,d为某一个人距离1号点的距离,当最后一个人逃出迷宫时的 t t t 为我们的答案,首先用DFS预处理出所有人距离1号点的距离。

距离1号点的最近的人a没有任何干扰,直接逃离迷宫,此时 t = d ( a ) t=d(a) t=d(a)

对于下一个人离1号点最近的b而言,此时必然存在 d ( b ) > = t d(b)>=t d(b)>=t,否则b就是先逃离迷宫的人了:

d ( b ) = t d(b)=t d(b)=t,说明b在某一个点肯定和上一个人a碰上了,本来b花费 t t t 时间也能到达1号点了,但因为和上一个人碰上,就得等待一个时刻,因为题意要求同一个点不能同时存在两个人,此时更新 t + + t++ t++

d ( b ) > t d(b)>t d(b)>t,说明上一个人逃离一段时间后,b才赶来,两人不会起冲突,此时总体花费的时间就是 d ( b d(b d(b)了,更新 t = d ( b ) t=d(b) t=d(b),重复上述过程,直到最后一个人离开。

#include
using namespace std;
const int maxn = 1e5+5;
int vis[maxn],d[maxn];
vectorG[maxn];
int num = 0;
void DFS(int fa,int x,int depth){
    if(vis[x])
        d[num++] = depth;
    for(int u : G[x]){
        if(u == fa)
            continue;
        DFS(x,u,depth+1);
    }
}
int main()
{
    int n;
    scanf("%d",&n);
    for(int i = 1;i<=n;i++)
        scanf("%d",&vis[i]);
    for(int i = 1;i

C.斐波那契数列

题意:定义 F i b n Fib_n Fibn为斐波那契数列,现在给定一个整数 R R R,求 ∑ n = 1 R F i b n & F i b n − 1 \sum_{n=1}^{R}{Fib_n\&Fib_{n-1}} n=1RFibn&Fibn1,答案对998244353。

思路:补起来稍微有点吃力的题目。
首先我们把 F i b n & F i b n − 1 Fib_n\&Fib_{n-1} Fibn&Fibn1分解开来,发现 F i b n & F i b n − 1 = F i b n − l o w b i t ( F i b n ) Fib_n\&Fib_{n-1} = Fib_n-lowbit(Fib_n) Fibn&Fibn1=Fibnlowbit(Fibn),
题目转变为求 ∑ n = 1 R F i b n − ∑ n = 1 R l o w b i t ( F i b n ) \sum_{n=1}^{R}{Fib_n}-\sum_{n=1}^{R}{lowbit(Fib_n)} n=1RFibnn=1Rlowbit(Fibn)

前者,网上有等比数列求和公式的求解过程, ∑ n = 1 R F i b n = F i b n + 2 − 1 \sum_{n=1}^{R}{Fib_n}=Fib_{n+2}-1 n=1RFibn=Fibn+21,单个斐波那契数的值则可以用矩阵快速幂解决,参见 poj3070。

后者,比较没有头绪,可以先打个表。发现 l o w b i t ( F i b n ) lowbit(Fib_n) lowbit(Fibn)是这样一个序列:
1   1   2   1   1   8   1   1   2   1   1   16   1 ⋯ 1\,1\,2\,1\,1\,8\,1\,1\,2\,1\,1\,16\,1\cdots 11211811211161,把 1   1   2   1   1 1\,1\,2\,1\,1 11211这个以6为单位循环出现的子序列去掉,剩下 8   16   8   32   8   16   8   64 ⋯ 8\,16\,8\,32\,8\,16\,8\,64\cdots 816832816864提取公因数 8 8 8,剩下序列为 1   2   1   4   1   2   1   8   1   2   1   4   1   2   1   16   1   2 ⋯ 1\,2\,1\,4\,1\,2\,1\, 8\,1\,2\,1\,4\,1\,2\,1\,16\,1\,2\cdots 1214121812141211612
假设这个特殊序列的长度为n,这个序列的特点就是:
1的个数为 n / 2 + ( n & ( 1 ) ) n/2 + (n\&(1)) n/2+(n&(1)),2的个数为 n / 4 + ( n & ( 2 ) ) n/4 + (n\&(2)) n/4+(n&(2)),4的个数为 n / 8 + ( n & ( 4 ) ) n/8 + (n\&(4)) n/8+(n&(4)) ⋯ \cdots 2 k 2^k 2k的个数为 n / 2 k + 1 + ( n & ( 2 k ) ) n/2^{k+1}+(n\&(2^k)) n/2k+1+(n&(2k))

然后求和,扣一扣余数细节,就大功告成了。这是我第一次写矩阵快速幂,顺手把 poj3070解决了,打表规律想得比较久…

#include
using namespace std;
const int mod = 998244353;
#define ll long long
ll c[2][2];
void multi(ll a[2][2],ll b[2][2],int n){
    memset(c,0,sizeof c);
    for(int i = 0;i>=1;
    }
}
ll solve(ll x){
    ll sum = 0;
    ll k = 1;
    while(k <= x){
        if(x & k)
            sum = (sum+(x/(2*k)+1)*k)%mod;
        else
            sum = (sum+x/(2*k)*k)%mod;
        k*=2;
    }
    return sum;
}
ll de[6] = {0,1,2,4,5,6};
int main()
{
    int T;
    scanf("%d",&T);
    while(T--){
        ll R;
        scanf("%lld",&R);
        ll p[2][2]={1,1,1,0};
        quick_pow(p,R+1);
        ll ans = (res[0][0]-1+mod)%mod;
        ll num = R/6;
        ans = (ans-num*6+mod)%mod;
        ans = (ans-8*solve(num)+mod)%mod;
        ans = (ans-de[R%6]+mod)%mod;
        printf("%lld\n",ans);
    }
    return 0;
}

D.二次函数
据说是初中数学题, f ( x ) = t a n ( x ) f(x)=tan(x) f(x)=tan(x)可解决,但没想出来,网上的题解也比较少。
暂未解决。

E.线性探查法

题意:假设有一个元素互不相同的正整数数组 a [ 1... n ] a[1...n] a[1...n],我们用以下方法得到数组 b [ 0... n − 1 ] b[0...n−1] b[0...n1]:初始时 b [ i ] b[i] b[i] 都为 -1,我们对 i = 1... n i=1...n i=1...n 依次插入 a [ i ] a[i] a[i],假设现在要插入的数是 x x x,首先我们找到 x % n x\%n x%n这个位置,如果 b [ x % n ] = − 1 b[x\%n]=−1 b[x%n]=1,则令 b [ x % n ] = x b[x\%n]=x b[x%n]=x,之后结束这次插入;否则看 b [ ( x + 1 ) % n ] b[(x+1)\%n] b[(x+1)%n] 是否等于 −1,如果等于则令 b [ ( x + 1 ) % n ] = x b[(x+1)\%n]=x b[(x+1)%n]=x,如果不等于,则继续看 ( x + 2 ) % n (x+2)\%n (x+2)%n…,直到找到一个位置。

完成所有插入后,我们会得到一个数组 b b b,现在给定这个数组 b b b,你需要求一个字典序最小的 a [ 1... n ] a[1...n] a[1...n]

思路:题意就是数据结构课本上的线性探查法,现场时候被线性探查四个字吸引住了。没想出正确解法,队友看出来这是一个拓扑排序题,关键在于建图,在队友帮助下把这题补了出来…

对于 b [ i ] b[i] b[i]而言,若 b [ i ] % n < i b[i]\%n <i b[i]%n<i,说明位置 b [ i ] % n − i b[i]\%n-i b[i]%ni上的数字都先于 b [ i ] b[i] b[i]插入。
b [ i ] % n > i b[i]\%n>i b[i]%n>i,说明位置 1 − ( b [ i ] % n − 1 ) 1-(b[i]\%n-1) 1(b[i]%n1) i − n i-n in上的数字都先于 b [ i ] b[i] b[i]插入。
按照拓扑序列的输出方法,输出一个字典序最小的拓扑排序即可。

#include
using namespace std;
const int maxn = 1e3+5;
vectorson[maxn];
struct node{
    int num,b,sum;
}G[maxn];
struct cmp{
    bool operator()(const node &X,const node &Y){
        return X.b > Y.b;
    }
};
int main()
{
    int n;
    scanf("%d",&n);
    for(int i = 0;i i){
            for(int j = G[i].b%n;j,cmp> Q;
    for(int i = 0;i

F.逆序对

题意:给定长度为 n 的两两不相同的整数数组 b [ 1... n ] b[1...n] b[1...n],定义 f ( y ) f(y) f(y) 为:将 b b b 每个位置异或上 y y y 后,得到的新数组的逆序对个数。现在你需要求 ∑ i = 1 m f ( i ) \sum_{i=1}^{m}f(i) i=1mf(i)由于答案可能很大,你只需要输出答案对 998244353 取模后的值。

思路:考虑原始序列中每两个点之间对答案的贡献。对于 b [ i ] b[i] b[i] b [ j ] b[j] b[j]( i < j i<j i<j)两个数字而言,假设 b [ i ] , b [ j ] b[i],b[j] b[i],b[j]二进制中最高位不同位是第 k k k位:若 b [ i ] < b [ j ] b[i]<b[j] b[i]<b[j],则第 k k k位为1的数与它们异或以后会产生一个逆序对,若 b [ i ] > b [ j ] b[i]>b[j] b[i]>b[j],则第 k k k位为0的数与它们异或以后,会产生一个逆序对。

问题转换成了求 1 − m 1-m 1m中二进制第 k k k位为1的数字有多少个。这个需要想一想。
考虑从最后一位开始向第 k k k位枚举,设当前位为 j j j,如果第 j j j位为1,则说明当第 j j j位为0时,余下的 j − 2 j-2 j2(第 k k k位固定为1)位是可以任取的,共有 1 < < ( j − 1 ) 1<<(j-1) 1<<(j1)个不同的数字。当模拟到第 k k k位时,此时判断第 k k k位如果是1,则当其后面均为零的数字与m之间的数字,都是第 k k k位为1的数字,具体可看代码实现。

#include
using namespace std;
int sum[32];
const int mod = 998244353;
int pos = 0;
void init(int m){
    memset(sum,0,sizeof sum);
    while(m > (1<=0;i--)
        for(int j = pos;j>=i;j--)
            if(i == j){
                if(m & 1<

G.抢红包机器人

题意:一共有m个红包,n个人,其中至少有一个机器人。给出每个红包抢到的顺序名单,比机器人快的都是机器人,机器人也有可能没有抢这个红包,问最少可能有几个机器人?

思路:签到题,枚举第几个是机器人,然后递归搜索顺序名单,把该方案是机器人的人都加起来,类比n个答案,选出最小的那个, n , m < 100 n,m<100 n,m<100,暴力去做的…代码就不贴了…

H.同构
据说是跟补图有关的题目。
暂未解决。

J.强壮的排列

题意:求偶数位比相邻两个数都大的,长度为n的 1 − n 1-n 1n 排列个数, n n n一定是奇数。由于答案可能较大,只需要输出答案对 998244353 取模后的值。

思路:考虑 s u m [ i ] sum[i] sum[i]为长度为 i i i的强壮排列,从 1 − i 1-i 1i为最大的数可能摆放的位置,则从 1 − i 1-i 1i枚举 j j j s u m [ i ] + = C i − 1 j − 1 ∗ s u m [ j − 1 ] ∗ s u m [ i − j ] sum[i]+=C{_{i-1}^{j-1}}*sum[j-1]*sum[i-j] sum[i]+=Ci1j1sum[j1]sum[ij],从 i − 1 i-1 i1个数中选出 j − 1 j-1 j1放到第 j j j位以前排,变成两种长度的序列进行组合。
n 2 n^2 n2的复杂度,会超时,于是我们就愉快的打表,而且只有奇数长度的排列,表是存的下的…

#include
#define ll long long
using namespace std;
const int mod = 998244353;
const int maxn = 1e5+5;
ll sum[maxn],inv[maxn],facts[maxn];
ll quick_pow(ll a,ll n){
    ll res = 1;
    while(n>0){
        if(n&1)
            res = res*a%mod;
        a = a*a%mod;
        n>>=1;
    }
    return res;
}
void init(){
    facts[0] = 1;
    inv[0] = 1;
    for(int i = 1;i

你可能感兴趣的:(赛后补题)