2019 CCPC-Wannafly Winter Camp Div2.day3(补题记录)

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

A.二十四点*
题意:初始有若干个数字在序列中,每次会抽出两个数字进行四则运算,将运算结果加入到序列中,当24出现在序列中时,我们称初始队列为一个好的序列。现在给定一个长度n的序列,问在它的所有非空子序列中,有多少个是好的?

思路:测试点只有两个,暴力DFS再加上一点点优化就能过,学习了一下如何利用二进制去枚举,能过div2,div1还需要想想别的方法。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1024+5;
int S[maxn],num[11];
int N;
struct node{
    vector<double> g;
};
bool BFS(){
    node ll;
    for(int i = 1;i<=N;i++){
        if(S[i] == 1)
            ll.g.push_back(num[i]);
    }
    queue<node>U;
    U.push(ll);
    while(!U.empty()){
        ll = U.front();
        U.pop();
        vector<double> g = ll.g;
        int n = g.size();
        for(int i = 0;i<n;i++){
            for(int j = i+1;j<n;j++){
                double a = g[i];
                double b = g[j];
                for(int k = 0;k<6;k++){
                    if(k == 0)
                        g.push_back(a+b);
                    else if(k == 1)
                        g.push_back(a-b);
                    else if(k == 2)
                        g.push_back(b-a);
                    else if(k == 3)
                        g.push_back(a*b);
                    else if(k == 4)
                        g.push_back(a/b);
                    else if(k == 5)
                        g.push_back(b/a);
                    if(fabs(g[n] - 24.0)< 0.01)
                        return 1;
                    node K;
                    for(int p = 0;p<g.size();p++)
                        if(p!=i && p!=j)
                            K.g.push_back(g[p]);
                    U.push(K);
                    g.pop_back();
                }
            }
        }
    }
    return 0;
}
int main()
{
    scanf("%d",&N);
    for(int i = 1;i<=N;i++)
        scanf("%d",&num[i]);
    int lol = pow(2,N);
    int ans = 0;
    for(int i = 1;i<lol;i++){
        int k = i;
        int tot = 1;
        while(k > 0){
            if(k%2 == 0)
                S[tot++] = 0;
            else
                S[tot++] = 1;
            k/=2;
        }
        if(BFS())
            ans++;
    }
    cout<<ans<<endl;
    return 0;
}

B.集合
题意:已知圆和圆外有两点x,y,求在圆上找一点m使得使得 d(x,m)+d(y,m) 尽可能地小。
几何题,暂留日后补…

暂未解决

F.小清新数论*
题意:给出一个整数 n,计算
在这里插入图片描述
思路:莫比乌斯反演+整除分块,给大家推荐几个博客…
题解:https://blog.csdn.net/ccsu_cat/article/details/86595562
莫比乌斯反演讲解:https://blog.csdn.net/qq_30974369/article/details/79087445
整除分块:https://blog.csdn.net/jerry99s/article/details/81867641

代码是我对着题解敲了一遍,第一次做莫比乌斯,感觉还需要再消化消化。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e7+1;
const int mod = 998244353;
int n;
int mu[maxn],vis[maxn],prime[maxn];
int getMu(){
    memset(vis,0,sizeof vis);
    mu[1] = 1;
    prime[0] = 0;
    for(int i = 2;i<=maxn;i++){
        if(!vis[i]){
            prime[++prime[0]] = i;
            mu[i] = -1;
        }
        for(int j = 1;j<=prime[0]&&i<=maxn/prime[j];j++){
            vis[i*prime[j]] = 1;
            if(i%prime[j]==0){
                mu[i*prime[j]] = 0;
                break;
            }
            mu[i*prime[j]]=-mu[i];
        }
    }
    for(int i = 2;i<=maxn;i++)
        mu[i] = mu[i-1]+mu[i];
}
long long di(int m){
    long long ans = 0;
    for(int l = 1,r;l<=m;l = r+1){
        r = m/(m/l);
        ans = (ans+1ll*(m/l)*(m/l)%mod*(mu[r]-mu[l-1]+mod))%mod;
    }
    return ans;
}
int main()
{
    getMu();
    scanf("%d",&n);
    long long ans = 0;
    for(int l = 1,r;l<=n;l = r+1){
        r = n/(n/l);
        ans = (ans+1ll*di(n/l)*(mu[r]-mu[l-1]+mod))%mod;
    }
    cout<<ans<<endl;
    return 0;
}

G.排列
题意:有一个长度为n的序列p,对于p[i]而言,其前缀为p[1],p[2]…p[i],是长度为i的前缀,定义前缀最小值A(pi)为min{p[i],p[2]…p[i]}。我们称前缀i<前缀j:当A(pi)<A(pj)或A(pi) =A(pj)而i<j,由此得到了另一个数组q,qi表示p中第i小的前缀长度。现给出序列q,求字典序最小的p。

思路:题目字典序最小隐含了p为一个1-n的排列,这题最难的应该是题意…手推一下样例:5 3 4 1 2 最小的前缀长度为5,所以1这个数应该是p[5],如果是p[4]/p[3],那最小的前缀长度就是4/3了,再往下推p[3]位置为2,但p[4]位置不一定是3,因为p[4]是3,4,5任何一个时,都有A(p4) = 2且4>3,都是可以是第三小的前缀,这个地方我们可以先不填。再往下发现p[4]这个位置应该是3了,如果填4,就不是字典序最小的。总结一下,会得出一个q[i-1]到q[i]增减性与p[i]的关系,当q[i]变小,p[i]填数字,q[i]变大,p[i]留空,填完所有数字,在留空的地方按字典序最小填上去就好了。

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int N;
    scanf("%d",&N);
    int q[N+1],p[N+1];
    for(int i = 1;i<=N;i++)
        scanf("%d",&q[i]);
    int cas = 1;
    p[q[1]] = cas;
    cas++;
    int last = q[1];
    for(int i = 2;i<=N;i++){
        if(q[i] < last){
            p[q[i]] = cas;
            cas++;
        }
        else
            p[q[i]] = 0;
        last = q[i];
    }
    int now = p[1];
    for(int i = 1;i<=N;i++){
        if(p[i] != 0)
            printf("%d",p[i]);
        else
            printf("%d",++now);
        if(i!=N)
            printf(" ");
    }
    return 0;
}

I.石头剪刀布
题意:n个选手参加石头剪刀布大赛,每人手上随机有一张石头剪刀布的卡牌,初始卡牌情况有 3^n种。第i个选手坐在第i个座位上,有m次事件,事件有两种:1 座位y上的选手挑战座位x上的选手,座位y被撤走,如果平判y负,胜者会坐在座位x上。2 查询某位选手存活的卡牌分配情况数。

思路:将每次挑战分为主客场,对于一位选手所存活的情况应该是3n(2/3)a(1/3)b,其中a是被挑战的次数,b是挑战的次数。我们把每次挑战的两个点以被挑战点为根结点连接起来,很容易得到一棵树。这时候询问某位选手的存活情况,如果这个选手存活,那么他一定是位于根结点的那个座位上的,所以这是对根结点状态的一个查询。由此想到并查集中的find()算法,把a、b作为并查集存储的信息,用带权并查集来处理。

代码是我借鉴其他博客写出来的。对于并查集合并操作中的减法,我的理解是:对于某位选手和他现在所处在的根结点而言,根结点在被这个选手挑战之前所进行的比赛场次是与这个选手存活情况无关的,在执行减法以后,这个选手原先结点就存储的是无关比赛场次,在查询的时候把根结点场次减去无关比赛场次,就可以得知答案了。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5+5;
const int mod = 998244353;
int rk[maxn],w[maxn],v[maxn],pre[maxn]; //w是比赛总场次,v是主场比赛场次
struct node{
    int fa,ww,vv;
    node(){};
    node(int f,int a,int b){
        fa = f;
        ww = a;
        vv = b;
    }
};
long long ksm(long long a,long long n){
    long long ans = 1;
    while(n>0){
        if(n%2 != 0)
            ans = ans*a%mod;
        a = a*a%mod;
        n/=2;
    }
    return ans;
}node Find(int x){
    if(x == pre[x]) return node(x,w[x],v[x]);
    node t =  Find(pre[x]);
    return node(t.fa,t.ww + w[x],t.vv + v[x]);
}void Merge(int x,int y){
    struct node t1 = Find(x);
    struct node t2 = Find(y);
    if(t1.fa != t2.fa){
        w[t1.fa]++;
        v[t1.fa]++;
        w[t2.fa]++;
        if(rk[t1.fa]>=rk[t2.fa]){
            w[t2.fa] -= w[t1.fa];
            v[t2.fa] -= v[t1.fa];
            rk[t1.fa]++;
            pre[t2.fa] = t1.fa;
        }
        else{
            w[t1.fa] -= w[t2.fa];
            v[t1.fa] -= v[t2.fa];
            rk[t2.fa]++;
            pre[t1.fa] = t2.fa;
        }
    }
}int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i = 1;i<=n;i++)
        pre[i] = i;
    long long sum = ksm(3,n);
    while(m--){
        int flag;
        scanf("%d",&flag);
        if(flag == 1){
            int x,y;
            scanf("%d%d",&x,&y);
            Merge(x,y);
        }
        else{
            int x;
            scanf("%d",&x);
            node t = Find(x);
            int a = t.vv,b = t.ww - t.vv;
            long long ans = sum*ksm(ksm(3,b),mod-2)%mod*ksm(2,a)%mod*ksm(ksm(3,a),mod-2)%mod;
            cout<<ans<<endl;
        }
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_44583165/article/details/87875736
今日推荐