【解题报告】活跃思想训练4(下) | CF 数论2000+ | 后五题

G:The LCMs Must be Large | CF 1166E

题意

  • The LCMs Must be Large | CF 1166E
    给定一个 n n n
    给定 m m m 组数据,每组数据是一个集合 S S S,每个集合的元素都在 [ 1 , n ] [1,n] [1,n] 的范围,且集合大小 < n <n <n
    问是否存在一个 a n a_n an,满足对于每一组数据,都满足 L C M ( S ) > L C M ( I − S ) LCM(S)>LCM(I-S) LCM(S)>LCM(IS)
    也就是选中的集合的数字的最小公倍数大于其他没有选中的元素的最小公倍数。
    1 ≤ m ≤ 50 1\le m\le 50 1m50
    1 ≤ n ≤ 1 0 4 1\le n\le 10^4 1n104

思路

  • 手膜一下,就会发现如果有两组数据满足他们的交是空集,那么一定没有答案。否则一定存在答案。
    证明很好做,就当课后习题吧(X
    我们可以通过构造法来证明答案存在。
    我们先拿出 m m m 个质数 p 1 , p 2 , ⋯   , p m p_1,p_2,\cdots,p_m p1,p2,,pm
    如果第 j j j 组数据包含 a i a_i ai,那么我们让 a i a_i ai 包含因子 p j p_j pj
    这样, L C M ( S ) = ∏ i = 1 m p i LCM(S)=\prod_{i=1}^m p_i LCM(S)=i=1mpi,且 L C M ( I − S ) < L C M ( S ) LCM(I-S)<LCM(S) LCM(IS)<LCM(S) 因为它不包含质因子 p j p_j pj
    且这样的要求是满足任意两组数据的交集非空。
  • 于是我们的问题就是如何判断交集非空。
    暴力 for 每两组数据,每次用一个 v i s vis vis 判断、回撤即可。

代码

  • 时间复杂度: O ( m 2 n ) O(m^2n) O(m2n)
#include<bits/stdc++.h>
using namespace std;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}
typedef long long ll;
const int MAX = 1e4+50;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;

int num[MAX];
vector<int>V[MAX];

int main()
{
    int m,n;scanf("%d%d",&m,&n);
    for(int i = 1;i <= m;++i){
        int si;scanf("%d",&si);
        while(si--){
            int t;scanf("%d",&t);
            V[i].push_back(t);
        }
    }

    for(int i = 1;i <= m;++i)
    for(int j = i + 1;j <= m;++j){
        bool jiao = false;
        for(auto it : V[i])num[it]++;
        for(auto it : V[j])if(num[it])jiao = true;

        if(!jiao){
            puts("impossible");
            return 0;
        }
        for(auto it : V[i])num[it]--;
    }
    puts("possible");
    return 0;
}

H:Calendar Ambiguity | CF 1389E

题意

  • Calendar Ambiguity | CF 1389E
    一年有 m m m 个月,每个月有 d d d 天,每 w w w 天为一周。
    问有多少对 ( x , y ) (x,y) (x,y),满足 x < y x<y x<y 且第 x x x 月的第 y y y 天和第 y y y 月的第 x x x 天是在一周的同一天。(比如都是周三,或者都是周 88 88 88 之类的)
  • 1000 1000 1000 组样例
    1 ≤ m , d , w ≤ 1 0 9 1\le m,d,w\le 10^9 1m,d,w109

思路

  • 一个很裸的数学题。
    列出式子,裸式子就是 ( x − 1 ) d + y ≡ ( y − 1 ) d + x ( m o d w ) (x-1)d+y\equiv (y-1)d+x \pmod w (x1)d+y(y1)d+x(modw)
    化简完之后就变成 x ( d − 1 ) ≡ y ( d − 1 ) ( m o d w ) x(d-1)\equiv y(d-1) \pmod w x(d1)y(d1)(modw)
    考虑到同余方程的除法操作,我们方程两边同时除以 d − 1 d-1 d1,模数要除以模数和除数的 gcd ⁡ \gcd gcd
  • 于是相当于我们要求 x ≡ y ( m o d p ) x\equiv y\pmod p xy(modp) 1 ≤ x < y ≤ min ⁡ ( m , d ) 1\le x<y\le \min(m,d) 1x<ymin(m,d) 的方案数
    考虑条件太严苛。我们记 min ⁡ ( m , d ) = u p \min(m,d)=up min(m,d)=up,我们可以先算出 1 ≤ x , y ≤ u p 1\le x,y\le up 1x,yup 的方案数
    然后减去 x = y x=y x=y 的方案数(共 e d ed ed 个),然后考虑对称性,除以二就是我们要求的答案
  • 于是相当于我们要求 x ≡ y ( m o d p ) x\equiv y\pmod p xy(modp) 1 ≤ x , y ≤ u p 1\le x,y\le up 1x,yup 的方案数
    我们按模 p p p 等价类划分。假设分成 k + 1 k+1 k+1 个段,其中前面的段的范围是 [ 1 , p ] , [ p + 1 , 2 p ] , ⋯ [1,p],[p+1,2p],\cdots [1,p],[p+1,2p],
    最后一个段是 [ k p + 1 , u p ] [kp+1,up] [kp+1,up]
    我们记 s 1 s1 s1 表示最后一个段的可选方案数。 s 2 s2 s2 表示 p − s 1 p-s1 ps1
    我们最终的答案就是 s 1 ∗ ( k + 1 ) 2 + s 2 ∗ k 2 s1*(k+1)^2+s2*k^2 s1(k+1)2+s2k2,可以想一想为什么

代码

  • 时间复杂度: O ( T log ⁡ ) O(T\log) O(Tlog)
#include<bits/stdc++.h>
using namespace std;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}
typedef long long ll;
const int MAX = 1e4+50;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;

int main()
{
    int T;
    cin >> T;
    while(T--){
        ll m,d,w;cin >> m >> d >> w;
        ll p = w / __gcd(w,d-1);
        ll up= min(m,d);
        ll k = up / p;
        ll s1,s2;
        s1 = up - k * p;
        s2 = p - s1;
        ll ans = s1 * (k + 1) * (k + 1) + s2 * k * k;
        ans -= min(m,d);
        ans /= 2;
        cout << ans << endl;
    }
    return 0;
}

I:Kate and imperfection | CF 1333F

题意

  • Kate and imperfection | CF 1333F
    给定一个集合 S = { 1 , 2 , 3 , ⋯   , n } S=\{1,2,3,\cdots,n\} S={1,2,3,,n}
    对于 I k I_k Ik,表示你要从中选出 k k k 个元素,对于所有的在 I k I_k Ik 的元素对 ( x , y ) (x,y) (x,y),你需要让 max ⁡ { gcd ⁡ ( x , y ) } \max\{\gcd(x,y)\} max{gcd(x,y)} 最小。
    对于每一个 I 2 , ⋯   , I n I_2,\cdots,I_n I2,,In,你都要求出这个最小值是多少
  • 2 ≤ n ≤ 5 ⋅ 1 0 5 2\le n\le 5\cdot 10^5 2n5105

思路

  • 我们可以先假定 n n n 很大。假设有 p p p 个质数,那么我们优先选这些质数好了, 1 1 1 选了也对答案没有影响。这样,直到 I p + 1 I_{p+1} Ip+1 仍然为 1 1 1
  • 再大一些,我们就必须要把合数加进来了。假设合数的质因子分解为 a ∗ b ∗ c a*b*c abc,且 a < b < c a<b<c a<b<c,那么它加进来的时候,此时 max ⁡ { gcd ⁡ ( x , y ) } \max\{\gcd(x,y)\} max{gcd(x,y)} 至少为 max ⁡ { a , b , c } \max\{a,b,c\} max{a,b,c}
    然后发现先加进来 4 4 4 一定是最优的。然后加 6 6 6 而不是加 8 8 8,因为 gcd ⁡ ( 4 , 8 ) = 4 > gcd ⁡ ( 3 , 6 ) = 3 \gcd(4,8)=4>\gcd(3,6)=3 gcd(4,8)=4>gcd(3,6)=3
    然后发现,我们新加进来的数字,加进来的最劣答案就是 x / m i n _ p r i m e x x/min\_prime_x x/min_primex
  • 也就是说,我们对于每一个数,都算出它的最小质因子,然后把这个数除以它的最小值因子,然后每次贪心取最小的,这样子是最优的。

代码

  • 时间复杂度: O ( n log ⁡ n ) O(n\log n) O(nlogn)
#include<bits/stdc++.h>
using namespace std;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}
typedef long long ll;
const int MAX = 5e5+50;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;

int tot;
bool vis[MAX];
int pri[MAX];
int mp[MAX];
int ans[MAX];
void shai(int n){
    mp[1] = 1;
    for(int i = 2;i <= n;++i){
        if(!vis[i]){
            pri[++tot] = i;
            mp[i] = i;
        }
        for(int j = 1;j <= tot && i * pri[j] <= n;++j){
            vis[i * pri[j]] = 1;
            mp[i * pri[j]] = pri[j];
            if(i % pri[j] == 0){
                break;
            }
        }
    }
    for(int i = 2;i <= n;++i){
        ans[i] = i / mp[i];
    }
    sort(ans+2,ans+n+1);
}
int main()
{
    int n;
    scanf("%d",&n);
    shai(n);
    for(int i = 2;i <= n;++i){
        printf("%d ",ans[i]);
    }
    return 0;
}

J:Infinite Path | CF 1327D

题意

  • J:Infinite Path | CF 1327D
    给定一个全排列 p n p_n pn
    给定一个颜色数组 c n c_n cn
    给定一个记号 p × p p\times p p×p 也就是让 p [ i ] = p [ p [ i ] ] p[i]=p[p[i]] p[i]=p[p[i]]
    给定一个记号 p k = ∏ i = 1 k p p^k=\prod_{i=1}^k p pk=i=1kp
    定义一条无穷路径也就是 p [ i ] , p [ p [ i ] ] , p [ p [ p [ i ] ] ] , ⋯ p[i],p[p[i]],p[p[p[i]]],\cdots p[i],p[p[i]],p[p[p[i]]],
  • 求出最小的 k k k k ≥ 1 k\ge 1 k1,满足 p k p^k pk 至少存在一条无穷路径,满足这条路径上的每个点的颜色都相同
  • 1 ≤ n ≤ 2 ⋅ 1 0 5 1\le n\le 2\cdot 10^5 1n2105

思路

  • (1)首先这个全排列一定组成一些环。我们每一个环都可以单独拿出来考虑。
    (2)这个环,环长为 c n t cnt cnt,则 p k p^k pk 产生的环数就是 gcd ⁡ ( k , c n t ) \gcd(k,cnt) gcd(k,cnt)
    我们可以 f o r for for 每个环,再 f o r for for 每一个 k k k ,再去判断这个新的环是否里面的颜色都相同。
    (3)但是貌似我们没必要 f o r for for 每一个 k k k,对于不同的 k k k 但是有相同的 gcd ⁡ \gcd gcd,这些 k k k 我只需要选一个即可,因为他们都是等价的。于是,我们选择的 k k k 的数量降到了因子的数量级 O ( c n t 1 / 3 ) O(cnt^{1/3}) O(cnt1/3)
    (4)我怎么快速知道 p k p^k pk 的每一个点是什么?
    我把一个环的点拿出来,记作 c 1 , c 2 , ⋯   , c c n t c_1,c_2,\cdots,c_{cnt} c1,c2,,ccnt
    p [ i ] = c ( i + 1 ) % c n t p[i]=c_{(i+1) \% cnt} p[i]=c(i+1)%cnt
    p [ p [ i ] ] = c ( i + 2 ) % c n t p[p[i]]=c_{(i+2) \% cnt} p[p[i]]=c(i+2)%cnt
    p k [ i ] = c ( i + k ) % c n t p^k[i]=c_{(i+k) \% cnt} pk[i]=c(i+k)%cnt

代码

  • 时间复杂度: O ( n 3 / 4 ) O(n^{3/4}) O(n3/4)
#include<bits/stdc++.h>
using namespace std;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}
typedef long long ll;
const int MAX = 5e5+50;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;

int ans;
int nxt[MAX];
int col[MAX];
bool vis[MAX];

vector<int>V;

int duo;
int duovis[MAX];

bool sam;

int t_per[MAX];


void dfs(int now,int c){
    if(col[now] != c){sam = false;}
    duovis[now] = duo;

    int nxxt = t_per[now];
    if(duovis[nxxt] != duo)dfs(nxxt,c);
}

void check(int cnt,int stp){
    duo++;

    for(int i = 0;i < cnt;++i){
        t_per[V[i]] = V[(i+stp)%cnt];
    }

    for(int i = 0;i < cnt;++i){
        int now = V[i];
        if(duovis[now] == duo)continue;
        sam = true;
        dfs(now,col[now]);
        if(sam){
            ans = min(ans,stp);
            return;
        }
    }
}

void work(int S){
    V.clear();
    int now = S;
    while(!vis[now]){
        vis[now] = 1;
        V.push_back(now);
        now = nxt[now];
    }
    int cnt = V.size();
    for(int i = 1;i * i <= cnt;++i){
        if(cnt % i == 0){
            check(cnt,i);
            check(cnt,cnt/i);
        }
    }
}

int main()
{
    int T;scanf("%d",&T);
    while(T--){
        int n;scanf("%d",&n);
        for(int i = 0;i < n;++i)vis[i] = 0;
        for(int i = 0;i < n;++i)scanf("%d",&nxt[i]),nxt[i]--;
        for(int i = 0;i < n;++i)scanf("%d",&col[i]);
        ans = n;
        for(int i = 0;i < n;++i){
            if(vis[i])continue;
            work(i);
        }
        printf("%d\n",ans);
    }
    return 0;
}

K:Multiples and Power Differences | CF 1485D

题意

  • Multiples and Power Differences | CF 1485D
    我的构造是真的垃圾…
    给定一个矩阵 a n , m a_{n,m} an,m
    你需要构造一个矩阵 b n , m b_{n,m} bn,m,满足以下要求:
    (1) a i , j ∣ b i , j a_{i,j}|b_{i,j} ai,jbi,j
    (2)对于 b b b 中边相邻的两个数值,他们的差是某个正整数的四次方。
    (3) 1 ≤ b i , j ≤ 1 0 6 1\le b_{i,j}\le 10^6 1bi,j106
  • 2 ≤ n , m ≤ 500 2\le n,m\le 500 2n,m500
    1 ≤ a i , j ≤ 16 1\le a_{i,j}\le 16 1ai,j16

思路

  • 差是某个正整数的四次方?这能做?
    好吧,有一个很神奇的提示: l c m ( 1 , 2 , 3 , ⋯   , 16 ) = 720720 lcm(1,2,3,\cdots,16)=720720 lcm(1,2,3,,16)=720720
    这个数字 < 1 0 6 <10^6 <106
  • 且答案非常稀少,暴力搜索剪枝也是过不了的。接下来就是神奇的构造
    如果 i + j i+j i+j 是个奇数,那么 b i , j = 720720 b_{i,j}=720720 bi,j=720720
    否则, b i , j = 720720 + a i , j 4 b_{i,j}=720720+a_{i,j}^4 bi,j=720720+ai,j4
    哇,看了之后发现好简单,但很难一下子想到…

代码

  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
#include<bits/stdc++.h>
using namespace std;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}
typedef long long ll;
const int MAX = 5e5+50;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;

int main()
{
    int n,m;cin >> n >> m;
    for(int i = 1;i <= n;++i){
        for(int j = 1;j <= m;++j){
            int t;cin >> t;
            if((i+j)&1)cout << 720720 << " ";
            else cout << 720720 + t * t * t * t << " ";
        }
        puts("");
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值