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

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

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

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

天梯赛加油~

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<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
int vis[maxn],d[maxn];
vector<int>G[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<n;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    DFS(0,1,0);
    sort(d,d+num);
    int t = 0;
    for(int i = 0;i<num;i++)
        if(t < d[i])
            t = d[i];
        else
            t++;
    cout<<t<<endl;
    return 0;
}

C.斐波那契数列

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

思路:补起来稍微有点吃力的题目。
首先我们把 F i b n &amp; F i b n − 1 Fib_n\&amp;Fib_{n-1} Fibn&Fibn1分解开来,发现 F i b n &amp; F i b n − 1 = F i b n − l o w b i t ( F i b n ) Fib_n\&amp;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 &ThinSpace; 1 &ThinSpace; 2 &ThinSpace; 1 &ThinSpace; 1 &ThinSpace; 8 &ThinSpace; 1 &ThinSpace; 1 &ThinSpace; 2 &ThinSpace; 1 &ThinSpace; 1 &ThinSpace; 16 &ThinSpace; 1 ⋯ 1\,1\,2\,1\,1\,8\,1\,1\,2\,1\,1\,16\,1\cdots 11211811211161,把 1 &ThinSpace; 1 &ThinSpace; 2 &ThinSpace; 1 &ThinSpace; 1 1\,1\,2\,1\,1 11211这个以6为单位循环出现的子序列去掉,剩下 8 &ThinSpace; 16 &ThinSpace; 8 &ThinSpace; 32 &ThinSpace; 8 &ThinSpace; 16 &ThinSpace; 8 &ThinSpace; 64 ⋯ 8\,16\,8\,32\,8\,16\,8\,64\cdots 816832816864提取公因数 8 8 8,剩下序列为 1 &ThinSpace; 2 &ThinSpace; 1 &ThinSpace; 4 &ThinSpace; 1 &ThinSpace; 2 &ThinSpace; 1 &ThinSpace; 8 &ThinSpace; 1 &ThinSpace; 2 &ThinSpace; 1 &ThinSpace; 4 &ThinSpace; 1 &ThinSpace; 2 &ThinSpace; 1 &ThinSpace; 16 &ThinSpace; 1 &ThinSpace; 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 &amp; ( 1 ) ) n/2 + (n\&amp;(1)) n/2+(n&(1)),2的个数为 n / 4 + ( n &amp; ( 2 ) ) n/4 + (n\&amp;(2)) n/4+(n&(2)),4的个数为 n / 8 + ( n &amp; ( 4 ) ) n/8 + (n\&amp;(4)) n/8+(n&(4)) ⋯ \cdots 2 k 2^k 2k的个数为 n / 2 k + 1 + ( n &amp; ( 2 k ) ) n/2^{k+1}+(n\&amp;(2^k)) n/2k+1+(n&(2k))

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

#include<bits/stdc++.h>
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<n;i++)
        for(int j = 0;j<n;j++)
            for(int k = 0;k<n;k++)
                c[i][j] = (c[i][j]+a[i][k]*b[k][j])%mod;
    for(int i = 0;i<n;i++)
        for(int j = 0;j<n;j++)
            a[i][j] = c[i][j];
}
ll res[2][2];
void quick_pow(ll a[2][2],ll n){
    memset(res,0,sizeof res);
    for(int i = 0;i<2;i++)
        res[i][i] = 1;
    while(n){
        if(n&1)
            multi(res,a,2);
        multi(a,a,2);
        n>>=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 &lt; i b[i]\%n &lt;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 &gt; i b[i]\%n&gt;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<bits/stdc++.h>
using namespace std;
const int maxn = 1e3+5;
vector<int>son[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<n;i++){
        G[i].num = i;
        G[i].sum = 0;
        scanf("%d",&G[i].b);
    }
    for(int i = 0;i<n;i++){
        if(G[i].b%n < G[i].num)
            for(int j = G[i].b%n;j<i;j++){
                G[i].sum++;
                son[j].push_back(i);
            }
        else if(G[i].b%n > i){
            for(int j = G[i].b%n;j<n;j++){
                G[i].sum++;
                son[j].push_back(i);
            }
            for(int j = 0;j<i;j++){
                G[i].sum++;
                son[j].push_back(i);
            }
        }
    }
    priority_queue<node,vector<node>,cmp> Q;
    for(int i = 0;i<n;i++)
        if(G[i].sum == 0)
            Q.push(G[i]);
    int tot = 0;
    node U = Q.top();
    while(!Q.empty()){
        struct node U = Q.top();
        Q.pop();
        if(tot == 0)
            printf("%d",U.b);
        else
            printf(" %d",U.b);
        tot++;
        for(int x : son[U.num]){
            G[x].sum--;
            if(G[x].sum == 0)
                Q.push(G[x]);
        }
    }
    return 0;
}

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 &lt; j i&lt;j i<j)两个数字而言,假设 b [ i ] , b [ j ] b[i],b[j] b[i],b[j]二进制中最高位不同位是第 k k k位:若 b [ i ] &lt; b [ j ] b[i]&lt;b[j] b[i]<b[j],则第 k k k位为1的数与它们异或以后会产生一个逆序对,若 b [ i ] &gt; b [ j ] b[i]&gt;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 &lt; &lt; ( j − 1 ) 1&lt;&lt;(j-1) 1<<(j1)个不同的数字。当模拟到第 k k k位时,此时判断第 k k k位如果是1,则当其后面均为零的数字与m之间的数字,都是第 k k k位为1的数字,具体可看代码实现。

#include<bits/stdc++.h>
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<<pos))
        pos++;
    pos-1;
    int now = 0;
    for(int i = pos;i>=0;i--)
        for(int j = pos;j>=i;j--)
            if(i == j){
                if(m & 1<<i){
                    now += (1<<i);
                    sum[j] += m-now+1;
                }
         break;
            }
            else if(m&(1<<j))
                sum[i] += (1<<(j-1));
}
int solve(int a,int b){
    long long c = a^b;
    int temp = 32;
    while(!(c&(1<<temp)))
        temp--;
    return temp;
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    init(m);
    int b[n+1];
    for(int i = 0;i<n;i++)
        scanf("%d",&b[i]);
    long long ans = 0;
    for(int i = 0;i<n;i++)
        for(int j = i+1;j<n;j++)
            if(b[i] < b[j])
                ans = (ans + sum[solve(b[i],b[j])])%mod;
            else
                ans = (ans + m - sum[solve(b[i],b[j])])%mod;
    cout<<ans<<endl;
    return 0;
}

G.抢红包机器人

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

思路:签到题,枚举第几个是机器人,然后递归搜索顺序名单,把该方案是机器人的人都加起来,类比n个答案,选出最小的那个, n , m &lt; 100 n,m&lt;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<bits/stdc++.h>
#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<maxn;i++){
        facts[i] = i*facts[i-1]%mod;
        inv[i] = quick_pow(facts[i],mod-2);
    }
}
ll C(ll m,ll n){
    return facts[n]*inv[n-m]%mod*inv[m]%mod;
}
void put(){
    sum[1] = 1;
    for(int i = 1;i<maxn;i+=2)
        for(int j = 2;j<=i;j+=2)
            sum[i] = (sum[i]+C(j-1,i-1)*sum[j-1]%mod*sum[i-j]%mod)%mod;
}
int main()
{
//    freopen("11.txt","w",stdout);
//    init();
//    put();
//    for(int i = 1;i<maxn;i+=2)
//        cout<<sum[i]<<",";
int ans[maxn/2];
int T;
    scanf("%d",&T);
    while(T--){
        int n;
        scanf("%d",&n);
        printf("%d\n",ans[n/2]);
    }
    return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值