• 17.8.10第五次测试


    1.公约数 (gcd.cpp\c\pas)

    【问题描述】 给定一个正整数,在[1,n]的范围内,求出有多少个无序数对(a,b)满足 gcd(a,b)=a xor b。
    
    【输入格式】 输入共一行,一个正整数n。
    
    【输出格式】 输出共一行,一个正整数表示答案。
    
    【输入输出样例】
    
    gcd.in 
    
    3
    
    gcd.out
    
    1
    
    解释:只有(2,3)满足要求
    
    【数据范围】 对于30%的数据满足n<=1000 对于60%的数据满足n<=10^5 对于100%的数据满足n<=10^7
    View Code

    题解:这是一道数学题,对,要推公式那种。最烦了。

    由给出的数据范围,骗个30分还是很容易的,最简单的暴力(我也就拿了这30。。。 Orz)暴力枚举两个数判断,复杂度O(n^2logn)。

    60分的:可以发现,a xor b=c  <==> a xor c=b。于是有了gcd(a,a xor c)==c,而c是a的约数。因此我们可以枚举c,再枚举a=i*c。因为n/1+n/2+n/3......+n/n=nlogn,加上gcd的logn,时间复杂度为O(nlog^2 n)。

    100分代码:a=b时无解,所以不妨设a>b。因gcd(a,b)<=a-b<=a xor b,所以有c=a-b。枚举c,a=i*c。因gcd(a,a-c)=c,只要判断a xor c=a-c即可。时间O(nlogn)。

    完整代码:

    #include <cstdio>
    using namespace std;
    int main(){
        freopen("gcd.in", "r", stdin);
        freopen("gcd.out", "w", stdout);
        int n,ans=0;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            for(int j=n/i;j>=2;j--){
                int a=i*j;
                if((a^i)==a-i)ans++;
            }
        printf("%d\n",ans);
    }
    View Code

    2.通讯 (message.cpp\c\pas)

    【问题描述】“这一切都是命运石之门的 选择。” 试图研制时间机器的机关 SERN 截获了中二科学家伦太郎发往过去的一条 短信,并由此得知了伦太郎制作出了电话微波炉(仮)。 为了掌握时间机器的技术,SERN 总部必须尽快将这个消息通过地下秘密通讯 网络,传达到所有分部。 SERN 共有 N 个部门(总部编号为 0),通讯网络有 M 条单向通讯线路,每条 线路有一个固定的通讯花费 Ci。 为了保密,消息的传递只能按照固定的方式进行:从一个已知消息的部门向 另一个与它有线路的部门传递(可能存在多条通信线路)。我们定义总费用为所 有部门传递消息的费用和。 幸运的是,如果两个部门可以直接或间接地相互传递消息(即能按照上述方法 将信息由 X 传递到 Y,同时能由 Y 传递到 X),我们就可以忽略它们之间的花费。 由于资金问题(预算都花在粒子对撞机上了),SERN 总部的工程师希望知道, 达到目标的最小花费是多少。
    
    【输入格式】多组数据,文件以 20 结尾。 每组数据第一行,一个整数 N,表示有 N 个包括总部的部门(从 0 开始编号)。 然后是一个整数 M,表示有 M 条单向通讯线路。 接下来 M 行,每行三个整数,Xi,Yi,Ci,表示第 i 条线路从 Xi 连向 Yi,花费 为 Ci。
    
    【输出格式】 每组数据一行,一个整数表示达到目标的最小花费。
    
    【输入输出样例】
    message.in
    
    3 3
    
    0 1 100
    
    1 2 50
    
    0 2 100
    
    3 3
    
    0 1 100
    
    1 2 50
    
    2 1 100
    
    2 2
    
    0 1 50
    
    0 1 100
    
    0 0
    
    message.out
    
    150
    
    100
    
    50
    
    【样例解释】第一组数据:总部把消息传给分部 1,分部 1 再传给分 部 2.总费用: 100+50=150. 第二组数据:总部把消息传给分部 1,由于分部 1 和分部 2 可以互相传递消 息,所以分部 1 可以无费用把消息传给 2.总费用:100+0=100. 第三组数据:总部把消息传给分部 1,最小费用为 50.总费用:50.
    
    【数据范围】对于 10%的数据, 保证 M=N-1 对于另 30%的数据,N ≤ 20 ,M ≤ 20 对于 100%的数据,N ≤ 50000 , M ≤ 10^5 ,Ci ≤ 10^5 ,数据组数 ≤ 5 数据保证一定可以将信息传递到所有部门。
    View Code

    题解:其实是模版题,但是当时忘了Taijan算法,算了都是泪。简单来说就是把整个图中的强连通分量缩成一个点,然后贪心从起点出发用最小的边把它们都连起来就好。

    完整代码:

    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<iostream>
    #include<stack>
    using namespace std ;
    
    #define maxn 50005
    const int inf=1<<30;
    int dfn[maxn],ord,bz[maxn],rd[maxn],low[maxn],head[maxn<<1],qy[maxn],ecnt,cnt,ans,m,n,u,v,w;
    stack<int>s;
    
    struct edge{
        int u,v,w,next;
        
    }E[maxn<<1];
    
    void addedge(int u,int v,int w){
        E[++ecnt].u=u;
        E[ecnt].v=v;
        E[ecnt].w=w;
        E[ecnt].next=head[u];
        head[u]=ecnt;
    }
    
    void Tarjan(int x){
        low[x]=dfn[x]=++ord;
        s.push(x);
        bz[x]=1;
        for(int i=head[x];i;i=E[i].next){
            int v=E[i].v;
            if(!dfn[v]){
                Tarjan(v);
                low[x]=min(low[v],low[x]);
            }
            else if(bz[v]&&dfn[v]<low[x])low[x]=dfn[v];
        }
        if(low[x]==dfn[x]){
            for(;;){
                int v=s.top();
                bz[v]=0;
                rd[v]=cnt;
                s.pop();
                if(v==x)break;
            }
            cnt++;
        }
    }
    
    void Work(){
        for(int i=0;i<n;i++){
            if(!dfn[i])Tarjan(i);
        }
        for(int i=0;i<cnt;i++){
            qy[i]=inf;
        }
        for(int i=0;i<n;i++){
            int u=rd[i];
            for(int j=head[i];j;j=E[j].next){
                int v=rd[E[j].v];
                if(u!=v)qy[v]=min(E[j].w,qy[v]);
            }
        }
        for(int i=0;i<cnt;i++){
            if(i==rd[0])continue;
            ans+=qy[i];
        }
    }
    
    void init(){
        memset(dfn,0,sizeof(dfn));
        memset(head,0,sizeof(head));
        memset(low,0,sizeof(low));
        memset(bz,0,sizeof(bz));
        memset(rd,0,sizeof(rd));
        ecnt=0;ord=0;ans=0;cnt=0;
    }
    
    int main(){
        freopen("message.in","r",stdin);
        freopen("message.out","w",stdout);
        while(1){
            scanf("%d%d",&n,&m);
            if(!n&&!m)break;
            init();
            for(int i=1;i<=m;i++){
                scanf("%d%d%d",&u,&v,&w);
                addedge(u,v,w);
            }
            Work();
            printf("%d\n",ans);
        }
    }
    View Code

    3.label (label.cpp/c/pas)

    【问题描述】 Samjia和Peter不同,他喜欢玩树。所以Peter送给他一颗大小为n的树,节 点编号从1到n。 Samjia要给树上的每一个节点赋一个[1,m]之间的权值,并使得有边直接相 连的两个节点的权值之差的绝对值 ≥ k。请你告诉Samjia有多少种不同的赋值 方案,只用求出答案对10 9+7(1000000007)取模得到的结果。
    
    【输入格式】 输入文件名为 label.in。 输入数据的第一行包含一个整数 T,代表测试数据组数。 接下来是 T 组数据. 每组数据的第一行包含三个整数 n、m 和 k。 接下来 n − 1 行,每行包含两个整数 u 和 v, 代表节点 u 和 v 之间有 一条树边。
    
    【输出格式】 输出文件名为 label.out。 对于每组数据,输出一行,包含一个整数,代表所求的答案。
    
    【输入输出样例】
    
    label.in
    
    3
    
    2 2 0
    
    1 2
    
    3 3 2
    
    1 3
    
    1 2
    
    3 3 1
    
    1 2
    
    2 3
    
    label.out
    
    4
    
    2
    
    12
    
    【输入输出样例说明】 对于第一组样例,满足的方案如下
    
    图中方括号内的数字([x])代表给节点赋的值。
    View Code

    题解:最有意思的一道题。f(x,y)代表x点取值y时符合条件的方案数,从 各子节点方案数f(v,i)(1<=i<=m&&|i-y|>=k)的和求积而更新;并且可以发现,对f(v,i)求和时很大一部分值是重复的。

    假如此时 m=10,k=2。树长这样 (1)--->(2)--->(3) 只有单链。
    可以发现什么?
    Dp[3][1]=dp[3][2]=……=dp[3][10]=1
    Dp[2][1]=dp[2][10]=8
    Dp[2][2]=dp[2][3]=……=dp[2][9]=7
    Dp[1][1]=dp[1][10]=57
    Dp[1][2]=dp[1][9]=50
    Dp[1][3]=dp[1][4]=……=dp[1][8]=51
    一个显而易见的是,同一个点的 dp 值是对称的。这个可以当作线段来看,父亲节点取一个值,和其距离k以外的就是子节点的取值范围。相当用短线遮挡一条长线,从两边遮,可选的部分数量是一样的。

    父 ---+---

    子  ////////-----------

        1   x   x+k         m

    就像这样。
    而且中间有一段的值是相同的。所以我只需关心有两端有多少个与中间一段的 dp 值。易得树的最大深度是 n-1=99,又因为 k<=100,所以 dp 值一端最多只有 99*100 个不同的,接下来就是一段相同的数。所以我只需设状态 dp[x][y]表示 x 节点的数是 y,且 x 的子树满足条件的方案数。要求 y<=(n-1)*k。中间的一段我只要算一个即可。

    上代码。

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #include<cmath>
    #include<stack>
    #define maxn 10010
    #define ll long long
    using namespace std;
    const int mod = 1e9 + 7;
    int ans,k,hl[maxn],fa[maxn],cnt,n,m,T,lim,f[110][maxn];
    struct Edge{
        int u,v,w,ne;
    }e[maxn<<1];
    void init()
    {
        memset(hl,0,sizeof(hl));
        memset(fa,0,sizeof(fa));
        memset(f,0,sizeof(f));
        memset(e,0,sizeof(e));
        cnt=ans=0;
    }
    void add(int u,int v)
    {
        e[++cnt].u=u;
        e[cnt].v=v;
        e[cnt].ne=hl[u];
        hl[u]=cnt;
    }
    ll getsum(int x,int st)
    {
        ll ret=0;
        for(int i=st;i<=lim;++i) ret=(ret+f[x][i])%mod;//起点在左区间 计算从起点到lim 
        for(int i=m;i>m-lim&&i>lim&&i>=st;--i) ret=(ret+f[x][m-i+1])%mod;//类似上一行 计算右区间 
        int l=max(st,lim+1),r=m-lim;//计算中区间范围 
        int len=r-l+1;
        if(len>0) ret=(ret+1ll*len*f[x][lim]%mod)%mod;
        return ret;
    }
    void dfs(int x)
    {
        for(int i=hl[x];i;i=e[i].ne){
            int v=e[i].v;
            if(v==fa[x]) continue;
            fa[v]=x;
            dfs(v);
        }
        for(int i=1;i<=lim;++i) f[x][i]=1;
        for(int i=hl[x];i;i=e[i].ne){
            int v=e[i].v;
            if(v==fa[x]) continue;
            ll sum=getsum(v,k+1);//初始值 
            for(int j=1;j<=lim;++j){
                if(j-k>=1) sum=(sum+f[v][j-k])%mod;//左端点增加区间 
                f[x][j]=1ll*f[x][j]*sum%mod;//乘法原理 
                int bj=j+k;//右端点
                if(bj<=m){//右端点还在大范围内
                    if(m-bj+1<=lim) bj=m-bj+1;//将右区间定位到左区间
                    else if(bj>=lim) bj=lim;//中区间定位到lim点 
                    sum=((sum-f[v][bj])%mod+mod)%mod;//右端点退出区间
                }
            }
        }
    }
    int ksm(int a,int B)
    {
        int x=a,b=B,ret=1;
        while(b){
            if(b&1) ret= 1ll*ret*x%mod;
            x= 1ll*x*x%mod;
            b>>=1;
        }
        return ret;
    }
    int main()
    {
        freopen("label.in","r",stdin);
        freopen("label.out","w",stdout);
        int x,y;
        scanf("%d",&T);
        while(T--){
            init();
            scanf("%d%d%d",&n,&m,&k);
            for(int i=1;i<n;++i){
                scanf("%d%d",&x,&y);
                add(x,y);
                add(y,x);
            }
            if(!k){
                printf("%d\n",ksm(m,n));
                continue;
            }
            lim=min(10000,m);
            dfs(1);
            printf("%d\n",getsum(1,1));
        }
        return 0;
    }
    View Code

    代码特别鸣谢@五十岚芒果酱

     大佬无私的支持hhh

  • 相关阅读:
    Luogu4433:[COCI2009-2010#1] ALADIN(类欧几里德算法)
    Bzoj4766: 文艺计算姬(Matrix-tree/prufer)
    Bzoj5019: [Snoi2017]遗失的答案
    [HAOI2009]逆序对数列(加强)
    CF850F Rainbow Balls
    Luogu4887 第十四分块(前体)
    Luogu2483 [SDOI2010]魔法猪学院(可并堆)
    导数积分表
    Bzoj2395: [Balkan 2011]Timeismoney(最小乘积生成树)
    Min_25筛
  • 原文地址:https://www.cnblogs.com/Requiescat/p/7347582.html
Copyright © 2020-2023  润新知