机房测试8.12

a

问题描述

n n 个青蛙, m m 个石头围成一圈编号为 0 m1 0   m − 1 ,第i 只青蛙每次跳 ai a i 步,这意味着青蛙能从石头 j mod m j   m o d   m 跳到石头 (j+ai) mod m ( j + a i )   m o d   m 。青蛙每跳一个石头,就占领它。每只青蛙最开始在0 号石头,它们可以一直跳下去。这些青蛙最后占领的石头编号和为多少?

输入

第一行一个整数T,接下来T 组数据,每组数据输入两行。
第一行输入两个整数n;m。第二行输入n 个整数ai。

输出

对于每组数据输出一行,形如”Case #X: Y”。X 为数据编号,从1 开始,Y
为占领的石头编号和。

数据范围

对于 30% 30 % 的数据, n10,m1e3 n ⩽ 10 , m ⩽ 1 e 3 ,
对于 100% 100 % 的数据, 1T50 1 ⩽ T ⩽ 50 , 1n1e4 1 ⩽ n ⩽ 1 e 4 , 1m1e9 1 ⩽ m ⩽ 1 e 9 , 1ai1e9 1 ⩽ a i ⩽ 1 e 9

解题法

对于每一只青蛙,跳到天荒地老也不一定能跳完所有石头。
仔细画图一瞧,占领的每一块石头正好相距 gcd(m,ai) g c d ( m , a i )
哇!其实是因为有一个神奇的方程保证有解的条件。
(我不可能知道的,具体请自己YY)

对于每一只青蛙,占据的石头一定是个等差数列:

0,gcd(m,ai),2gcd(m,ai),,mgcd(m,ai),(m) 0 , g c d ( m , a i ) , 2 ∗ g c d ( m , a i ) , ⋯ , m − g c d ( m , a i ) , ( m )

! 其实没有m这一项 !

故和就是:

m22(gcd(m,ai)+1)m m 2 2 ∗ ( g c d ( m , a i ) + 1 ) − m

开一个 bool 数组,记录是否访问过。 30分get!

那么问题来了,如何去重呢?

30100 30 → 100 的蜕变,仅需一步:

容斥

针对因数的容斥

将gcd们排好序,由小到大,每一个有船新贡献的因数必定至少含有至少一个船新质因数,就此可以进行估计,1e9的数据,只需很少( 20 ⩽ 20 个)个因数就可以搞出所有被占据的石头。

代码

#include<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
#define FN "a"

const int maxn=1e4+5;

void Cin(int &x) {
    char ch;
    while(!isdigit(ch=getchar()));
    for(x=ch-'0';isdigit(ch=getchar());x=(x<<3)+(x<<1)+ch-'0');
}

int n,m;

int a[maxn],gc[maxn],que[66];

int gcd(int a,int b) {return b?gcd(b,a%b):a;}

int lcm(int a,int b) {return 1LL*a/gcd(a,b)*b;}

long long sum(int x) {return 1LL*m*(m/x+1)/2-m;}

int main() {
    freopen(FN".in","r",stdin);
    freopen(FN".out","w",stdout);
    int T;
    Cin(T);
    for(int t=1;t<=T;t++) {
        Cin(n);
        Cin(m);
        for(int i=1;i<=n;i++) {
            Cin(a[i]);
            gc[i]=gcd(m,a[i]);
        }
        std::sort(gc+1,gc+n+1);
        int cnt=0;
        for(int i=1;i<=n;i++) {
            bool fl=false;
            for(int j=1;j<=cnt;j++)
                if(!(gc[i]%que[j]))
                    fl=true;
            if(fl)
                continue;
            que[++cnt]=gc[i];
        }
        long long ans=0;
        for(int s=1;s<(1<<cnt);s++) {
            int d=1,fl=-1;
            for(int i=1;i<=cnt;i++)
                if(s>>(i-1)&1) {
                    fl=-fl;
                    d=lcm(d,que[i]);
                }
            ans+=fl*sum(d);
        }
        printf("Case #%d: %I64d\n",t,ans);
    }
    return 0;
}

b

问题描述

对于数字 n n ,[1,n] 内,有多少个数可以被由它转化成的二进制里面的1 的个数整除
比如9 的二进制形式为1001,但是 9%20 9 % 2 ≠ 0 ,所以它不是
比如8 的二进制形式为1000, 8%1=0 8 % 1 = 0 ,所以它是
如对于9 来说 [1,9] [ 1 , 9 ] 内有5 个数字是合法的。

数据范围

对于 20% 20 % 的数据, n50000000 n ⩽ 50000000 ,
对于 100% 100 % 的数据, 1n1e19 1 ⩽ n ⩽ 1 e 19

解题法

数位DP,唯一问题是可能有点卡空间,四维数组还是很夸张的。

233的作用只是为了不写 memset() m e m s e t ( )

代码

#include<cstdio>
#include<iostream>
#define FN "b"

unsigned long long N,ans;
int digit[70],figure,vis[70][70][70][2];
unsigned long long dp[70][70][70][2];

unsigned long long dfs(int fig,bool tag,int sum,int num,int last,int _233_) {
    if(vis[fig][num][last][tag]==_233_)
        return dp[fig][num][last][tag];
    if(!fig) {
        if(!last && sum==num)
            return dp[fig][num][last][tag]=1;
        return dp[fig][num][last][tag]=0;
    }
    int h=tag?digit[fig]:1;
    unsigned long long ANS=0;
    for(int i=h;i>=0;i--)
        ANS+=dfs(fig-1,tag && (i==h),sum,num+i,((last<<1)%sum+i)%sum,_233_);
    vis[fig][num][last][tag]=_233_;
    return dp[fig][num][last][tag]=ANS;
}

int main() {
    freopen(FN".in","r",stdin);
    freopen(FN".out","w",stdout);
    std::cin>>N;
    while(N) {
        digit[++figure]=(int) N&1;
        N>>=1;
    }
    for(int i=1;i<=figure;i++)
        ans+=dfs(figure,true,i,0,0,i);
    std::cout<<ans;
    return 0;
}

c

问题描述

有一个 nm n ∗ m 的矩阵,左上角为 (1,1) ( 1 , 1 ) 右下角为 (n,m) ( n , m ) ,每个格子边长为4,每个格子中央有权值为 aij a i j 。对于一个格子的顶点,这个点的权值是 ni=1mj=1dis2aij ∑ i = 1 n ∑ j = 1 m d i s 2 ∗ a i j , dis 是这个点到格子 (i,j) ( i , j ) 中心的欧几里得距离。求该权值最小的顶点。如有相同权值的顶点,首先选择x 坐标较小的顶点,如果还有相同,再选择y 坐标较小的顶点。

数据范围

对于 20% 20 % 的数据, norm=1 n o r m = 1 ,
对于 100% 100 % 的数据, 1n,m1000 1 ⩽ n , m ⩽ 1000 , 1aij100000 1 ⩽ a i j ⩽ 100000

解题法

dis问什么要给你平方?

当然是要化成横和纵分开算啊!

横纵预处理费用, n2 n 2 暴力找最小。

如何预处理?

拍扁!

对于一个纵列,需要算出 ni=022a[i] ∑ i = 0 n ∗ 2 2 ∗ a [ i ]

处理出横纵两个数组就好了。

一句话来说:纵列算横边(2),横行算纵边(还是2)。

官方给的题解竟然有扫描线这几个字!

迷醉。

代码

#include<bits/stdc++.h>
#define FN "c"

const int maxn=1005;

long long a[maxn][maxn],c[maxn],l[maxn];
long long ansx[maxn],ansy[maxn];

int main() {
    freopen(FN".in","r",stdin);
    freopen(FN".out","w",stdout);
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++)
        for(int j=0;j<m;j++) {
            scanf("%I64d",&a[i][j]);
            c[i]+=a[i][j];
            l[j]+=a[i][j];
        }
    for(int i=0;i<=n;i++) {
        for(int j=i-1,x=2;j>=0;j--,x+=4)
            ansx[i]+=c[j]*x*x;
        for(int j=i,x=2;j<n;j++,x+=4)
            ansx[i]+=c[j]*x*x;
    }
    for(int i=0;i<=m;i++) {
        for(int j=i-1,x=2;j>=0;j--,x+=4)
            ansy[i]+=l[j]*x*x;
        for(int j=i,x=2;j<m;j++,x+=4)
            ansy[i]+=l[j]*x*x;
    }
    long long temp=1e18;
    int posx,posy;
    for(int i=0;i<=n;i++)
        for(int j=0;j<=m;j++)
            if(ansx[i]+ansy[j]<temp) {
                temp=ansx[i]+ansy[j];
                posx=i;
                posy=j;
            }
    printf("%I64d\n%d %d",temp,posx,posy);
    return 0;
}

总结(讲垃圾话)

今天玩Hi了,T2竟然完全没有想到数位DP,感觉DP宛若没学过一般(其实因为我看1e19就去想数学去了)

T1其实已经差不多了,结果没有正确估计船新因子数,导致不敢写。

这一次尻♂试已经将我拉回大众水平了。

多想想!

好像最近有点贪♂Van了,控制游戏时间啊!

此上。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值