【解题报告】CF#747 Div2 | A - F

A. Consecutive Sum Riddle *800

题意

  • 给定 n n n,你需要要给出 l , r l,r l,r,满足
    ∑ i = l r i = n \sum_{i=l}^r i=n i=lri=n
  • 样例组数 ≤ 1 0 4 \le 10^4 104
    1 ≤ n ≤ 1 0 18 1\le n\le 10^{18} 1n1018
    − 1 0 18 ≤ l < r ≤ 1 0 18 -10^{18}\le l<r\le 10^{18} 1018l<r1018

思路

  • 注意到我们的范围可以是负的,所以可以负的一块和正的一块抵消,就是构造
    ( ( − n + 1 ) + ⋯ + ( − 1 ) ) + 0 + ( 1 + 2 + ⋯ ( n − 1 ) ) + n = n ((-n+1)+\cdots+(-1))+0+(1+2+\cdots (n-1))+n=n ((n+1)++(1))+0+(1+2+(n1))+n=n

代码

  • 时间复杂度: O ( T ) O(T) O(T)
#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 = 1e5+50;
const int MOD = 1e9+7;
 
int main()
{
    int n;cin >> n;
    while(n--){
        ll tt;
        cin >> tt;
        cout << -tt+1 << " " << tt << endl;
    }
    return 0;
}

B. Special Numbers *1100

题意

  • 给定一个 n n n,我们称一个数特殊,当且仅当这个数可以被写成一个或多个 n n n不同幂次 的和。
    问你第 k k k 小的这样的数是几
  • 样例组数 ≤ 1 0 4 \le 10^4 104
    2 ≤ n ≤ 1 0 9 2\le n\le 10^9 2n109
    1 ≤ k ≤ 1 0 9 1\le k\le 10^9 1k109

思路

  • 不同幂次的和,不就是求出 n n n 进制下的第 k k k 小的数是多少嘛!
    直接把 k k k 拆成二进制的数字,这一位为 1 1 1,那么这个数就加上 n c n^c nc 即可

代码

  • 时间复杂度: O ( T log ⁡ k ) O(T\log k) O(Tlogk)
#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 = 1e5+50;
const int MOD = 1e9+7;
 
 
int main()
{
    int T;cin >> T;
    while(T--){
        int n,k;
        cin >> n >> k;
        queue<int>S;
        while(k){
            if(k&1)S.push(1);
            else S.push(0);
            k >>= 1;
        }
        ll tmp = 1;
        ll ans = 0;
        while(!S.empty()){
            if(S.front() == 1)ans = (ans + tmp) % MOD;
            tmp = tmp * n % MOD;
            S.pop();
        }
        cout << ans << endl;
    }
    return 0;
}

C. Make Them Equal *1200

题意

  • 给你一个长度为 n n n 的字符串 s [ 1 ] . . . s [ n ] s[1]...s[n] s[1]...s[n],给你一个目标字符 c c c
    一次操作,选择一个位置 i i i,然后对于所有的位置 j j j,满足 i ∤   j i\not|\ j i j,让 s [ j ] = c s[j]=c s[j]=c
    问你最少操作次数,和相应操作的位置,让这个字符串的每一个位置都变成 c c c
  • 样例组数 ≤ 1 0 4 \le 10^4 104
    3 ≤ n ≤ 1 0 5 3\le n\le 10^5 3n105
    ∑ n ≤ 3 ⋅ 1 0 5 \sum n\le 3\cdot10^5 n3105

思路

  • 若字符串都是 c c c,自然是 0 0 0 次了。
    若字符串都不是 c c c,那也最多操作两次,选择位置 n − 1 , n n-1,n n1,n 即可。
  • 接下来就是一次操作的情况。我们暴力枚举位置为 i i i,只需要看所有的 i k ik ik 位置是否都是 c c c 即可。
    这样暴力做,根据调和级数,复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)
  • 还有别的更优做法。我们只操作一次,只要选择最后的 c c c 的位置 i i i,且这个位置满足 2 i > n 2i>n 2i>n即可,我们选择操作这个位置即可。复杂度为 O ( n ) O(n) O(n)

代码

  • 时间复杂度: 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 = 3e5+50;
const int MOD = 1e9+7;
 
char ss[MAX];
 
int main()
{
    int T;scanf("%d",&T);
 
    while(T--){
        int n;char aim[5];
        scanf("%d%s",&n,aim);
        int diff = 0;
        scanf("%s",ss+1);
        for(int i = 1;i <= n;++i){
            if(ss[i] != aim[0])diff++;
        }
 
        if(diff == 0){
            puts("0");
            continue;
        }
 
        bool fd = false;
 
        for(int i = 1;i <= n;++i){
            diff = 0;
            for(int j = i;j <= n;j += i){
                if(ss[j] != aim[0])diff++;
            }
            if(diff == 0){
                fd = true;
                puts("1");
                printf("%d\n",i);
                break;
            }
        }
 
        if(!fd){
            puts("2");
            printf("%d %d\n",n-1,n);
        }
 
 
    }
    return 0;
}

D. The Number of Imposters *1700

题意

  • n n n 个点,每个点值为 0 / 1 0/1 0/1
    m m m 条边,每条边连的两个点告诉你是值相同或者不同的
    求值为 0 0 0 的点数量的最大值
  • 若有矛盾的,输出 − 1 -1 1
  • 1 ≤ n ≤ 2 ⋅ 1 0 5 1\le n\le 2\cdot 10^5 1n2105
    0 ≤ m ≤ 5 ⋅ 1 0 5 0\le m\le 5\cdot 10^5 0m5105

思路

  • 感觉很早之前有过?但是当时的我肯定直接 PASS 了,但是现在貌似感觉很简单了。
    首先,假设每个点都是 1 1 1,并且用带权并查集去记录每个点所在连通块的所有点的编号,还有连通块的大小
  • 合并,使用 d s u dsu dsu ,若两个点之间连边了,可以使用启发式合并,把连通块大小,小的加入大的。
    然后考虑他们值的变化。若连边让这两个值不同,但这两个值相同了,我们可以让那个小的连通块中的所有点都 0 , 1 0,1 0,1 互换。
    若这两个点在同一个连通块中,那么需要判断非法性。
  • 当然,直接使用暴力 d f s dfs dfs ,使用完整建图的方式也可以做,和这种做法类似。

代码

  • 时间复杂度: 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 = 2e5+50;
const int MOD = 1e9+7;

int col[MAX];
int fa[MAX];
int cnt[MAX];
 
int find_fa(int x){
    if(x == fa[x])return x;
    return fa[x] = find_fa(fa[x]);
}
 
bool sam(int x,int y){
    return find_fa(x) == find_fa(y);
}
 
vector<int>TONG[MAX];
 
void add(int x,int y){
    int fx = find_fa(x);
    int fy = find_fa(y);
    if(fx != fy){
        if(cnt[fx] < cnt[fy]){
            for(auto it : TONG[fx]){
                TONG[fy].push_back(it);
            }
            TONG[fx].clear();
            fa[fx] = fy;
            cnt[fy] += cnt[fx];
        }else{
            for(auto it : TONG[fy]){
                TONG[fx].push_back(it);
            }
            TONG[fy].clear();
            fa[fy] = fx;
            cnt[fx] += cnt[fy];
        }
 
    }
}
 
int main()
{
    int T;T = read();
 
    while(T--){
        int n,m;
        n = read();m = read();
        for(int i = 1;i <= n;++i){
            fa[i] = i;
            col[i] = 1;
            cnt[i] = 1;
            TONG[i].clear();
            TONG[i].push_back(i);
        }
 
        bool contra = false;
 
        while(m--){
            int i,j,k;char op[20];
            i = read();j = read();Getstr(op,k);
            if(contra)continue;
 
            int fi = find_fa(i);
            int fj = find_fa(j);
            if(op[0] == 'i'){
                if(fi == fj){
                    if(col[i] == col[j]) contra = true;
                }else{
                    if(col[i] == col[j]){
                        if(cnt[fi] < cnt[fj]){
                            for(auto it : TONG[fi]){
                                col[it] ^= 1;
                            }
                        }else{
                            for(auto it : TONG[fj]){
                                col[it] ^= 1;
                            }
                        }
                    }
                    add(fi,fj);
                }
            }else{
                if(fi == fj){
                    if(col[i] != col[j]) contra = true;
                }else{
                    if(col[i] != col[j]){
                        if(cnt[fi] < cnt[fj]){
                            for(auto it : TONG[fi]){
                                col[it] ^= 1;
                            }
                        }else{
                            for(auto it : TONG[fj]){
                                col[it] ^= 1;
                            }
                        }
                    }
                    add(fi,fj);
                }
            }
        }
 
 
        if(contra){
            puts("-1");
            continue;
        }
        int ans = 0;
        for(int i = 1;i <= n;++i){
            if(TONG[i].size()){
                int cnt1 = 0,cnt2 = 0;
                for(auto it : TONG[i]){
                    if(col[it] == 0)cnt1++;
                    else cnt2++;
                }
                ans += max(cnt1,cnt2);
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

E1. Rubik’s Cube Coloring (easy version) *1300

题意

  • 给定一个层数为 k k k满二叉树。结点编号为标准的层序遍历的编号。
    魔方有六个面,每个面一个颜色
    请添加图片描述
  • 树上的结点的颜色也是这六个颜色之一。
    但是两个相邻结点的颜色必须是魔方中,颜色相邻的两种颜色。
    求这个满二叉树的合法染色的方案数取模 1 e 9 + 7 1e9+7 1e9+7
  • 1 ≤ k ≤ 60 1\le k\le 60 1k60

思路

  • 假设固定根,每个节点必须和其父节点颜色要满足要求。
    不管父节点颜色如何,这个节点都只有 4 4 4 种颜色可以选。
    答案就是 6 × 4 节 点 个 数 − 1 6\times 4^{节点个数-1} 6×41
    节点个数是 2 k − 1 2^k-1 2k1
  • 这里 2 k 2^k 2k 并不会爆 l l ll ll,可以直接去算。
    若你要取模的话,根据欧拉降幂,请取模 φ ( 1 e 9 + 7 ) = 1 e 9 + 6 \varphi(1e9+7)=1e9+6 φ(1e9+7)=1e9+6

代码

  • 时间复杂度: O ( l o g k ) O(log k) O(logk)
#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 = 2e5+50;
const int MOD = 1e9+7;
 
ll qpow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a%MOD;a=a*a%MOD;n>>=1;}return res;}
ll qpow(ll a,ll n,ll p){a%=p;ll res = 1LL;while(n){if(n&1)res=res*a%p;a=a*a%p;n>>=1;}return res;}
ll npow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a;a=a*a;n>>=1;if(res<0||a<0)return 0;}return res;}
ll inv(ll a){/* */return qpow(a,MOD-2);}
ll inv(ll a,ll p){return qpow(a,p-2,p);}
 
int main()
{
    ll k;
    cin >> k;
    ll tmp = (qpow(2,k,MOD-1) - 2 + (MOD-1)) % (MOD-1);
    cout << 1LL * 6 * qpow(4,tmp) % MOD;
    return 0;
}

E2. Rubik’s Cube Coloring (hard version) *2300

题意

  • 其他条件和上题的条件相同
    这里已知 n n n 个点,给定每个点的编号和颜色
    求这个树的合法染色的方案数取模 1 e 9 + 7 1e9+7 1e9+7
  • 1 ≤ n ≤ min ⁡ ( 2 k − 1 , 2000 ) 1\le n\le \min(2^k-1,2000) 1nmin(2k1,2000)

思路

  • 根据上面条件的分析,若没有已知颜色的点,仍然儿子节点的方案数是 4 4 4
    我们把每个已知的点,这个点到根节点的所有点,边全部拉出来,变成一个新的树 T T T
    首先有一个结论:设 f ( T ) f(T) f(T) 表示这个树的合法染色的方案数, s z ( T ) sz(T) sz(T) 表示这个树的点的个数
    那么最终的答案为:
    f ( T ) × 4 2 k − 1 − s z ( T ) f(T)\times 4^{2^k-1-sz(T)} f(T)×42k1sz(T)
    因为那些不在 T T T 上的结点,都有 4 4 4 种可选的方案。
  • 接下来我们考虑怎么去求 f ( T ) f(T) f(T)
    注意到,我们对于每一个题目给定的结点,最多拉出 k k k 个节点到 T T T 上,所以 O ( s z ( T ) ) = n k O(sz(T))=nk O(sz(T))=nk
    考虑到固定一些颜色求方案数,我们可以写一个 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示 i i i 节点选择颜色 j j j ,在 T T T 中以它为根,它的子树的合法染色方案数为 d p [ i ] [ j ] dp[i][j] dp[i][j]
    那么我们可以很简单地去进行转移。
  • 还有一个是如何建树的问题。首先判断这个节点是第几层,是很简单的,节点编号减去 1 , 2 , 4 , ⋯ 1,2,4,\cdots 1,2,4, ,直到再减就变负了为止,就能算出正确的层数,也能知道这个节点是这一层的从左往右数第几个,设为第 n u m num num 个( 0 − i n d e x 0-index 0index
    然后想一下,这个 n u m num num 与从根节点,每次往左还是往右走,手算一下
    n u m = 0 , L L L num=0,LLL num=0,LLL
    n u m = 1 , L L R num=1,LLR num=1,LLR
    n u m = 2 , L R L num=2,LRL num=2,LRL
    n u m = 3 , L R R num=3,LRR num=3,LRR

    然后发现是和这个数的二进制有关的,随便搞搞就可以了

代码

  • 时间复杂度: O ( 6 n k ) O(6nk) O(6nk)
#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 = 3e5+50;
const int MOD = 1e9+7;
 
ll qpow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a%MOD;a=a*a%MOD;n>>=1;}return res;}
ll qpow(ll a,ll n,ll p){a%=p;ll res = 1LL;while(n){if(n&1)res=res*a%p;a=a*a%p;n>>=1;}return res;}
ll npow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a;a=a*a;n>>=1;if(res<0||a<0)return 0;}return res;}
ll inv(ll a){/* */return qpow(a,MOD-2);}
ll inv(ll a,ll p){return qpow(a,p-2,p);}
 
int tot;
int lson[MAX],rson[MAX];
int col[MAX];
 
ll dp[MAX][7];
map<string,int>M;
 
bool cant[7][7];
 
void dfs(int x){
    if(lson[x])dfs(lson[x]);
    if(rson[x])dfs(rson[x]);
 
 
 
    if(col[x]){
        ll sum0 = 0;
        ll sum1 = 0;
 
        if(!lson[x])sum0 = 1;
        if(!rson[x])sum1 = 1;
 
        for(int i = 1;i <= 6;++i){
            if(cant[i][col[x]])continue;
            if(lson[x])
                sum0 = (sum0 + dp[lson[x]][i]) % MOD;
            if(rson[x])
                sum1 = (sum1 + dp[rson[x]][i]) % MOD;
        }
        dp[x][col[x]] = sum0 * sum1 % MOD;
 
    }else{
        for(int j = 1;j <= 6;++j){
            ll sum0 = 0;
            ll sum1 = 0;
 
            if(!lson[x])sum0 = 1;
            if(!rson[x])sum1 = 1;
 
            for(int i = 1;i <= 6;++i){
                if(cant[i][j])continue;
                if(lson[x])
                    sum0 = (sum0 + dp[lson[x]][i]) % MOD;
                if(rson[x])
                    sum1 = (sum1 + dp[rson[x]][i]) % MOD;
            }
            dp[x][j] = sum0 * sum1 % MOD;
        }
    }
 
}
 
void add(int& x,int bs,ll num,int c){
    if(x == 0){
        x = ++tot;
    }
    if(bs == 0){
        col[x] = c;
    }else{
        if(num&1)
            add(rson[x],bs-1,num>>1,c);
        else
            add(lson[x],bs-1,num>>1,c);
    }
}
 
 
void init(){
    M["white"] = 1;
    M["yellow"] = 2;
    M["green"] = 3;
    M["blue"] = 4;
    M["red"] = 5;
    M["orange"] = 6;
 
    for(int i = 1;i <= 6;++i)cant[i][i] = 1;
    cant[1][2] = 1;
    cant[2][1] = 1;
    cant[3][4] = 1;
    cant[4][3] = 1;
    cant[5][6] = 1;
    cant[6][5] = 1;
}
 
int main()
{
    init();
    ll k,n;
    int root = 0;
    cin >> k >> n;
    for(int i = 1;i <= n;++i){
        ll num,c;
        string ss;
        cin >> num >> ss;
        int ceng = 1;
        ll bs = 1;
        while(bs < num){
            num -= bs;
            bs = bs * 2;
            ceng++;
        }
        num--;
        ceng--;
        c = M[ss];
 
        ll num2 = 0;
        for(int i = 0;i < ceng;++i){
            num2<<=1;
            if(num&1)num2|=1;
            num>>=1;
        }
        add(root,ceng,num2,c);
 
    }
 
 
    dfs(root);
 
//    for(int i = 1;i <= tot;++i){
//        show(i,lson[i],rson[i]);
//        for(int j = 1;j <= 6;++j){
//            show(dp[i][j]);
//        }
//    }
 
    ll ans = 0;
    for(int i = 1;i <= 6;++i){
        ans = (ans + dp[root][i] * qpow(4,(1LL << k) - 1 - tot) % MOD) % MOD;
    }
    cout << ans;
 
    return 0;
}

F. Ideal Farm *2400

题意

  • 给你 s , n , k s,n,k s,n,k
    一个合法序列,满足 ( ∑ i = 1 n a [ i ] ) = s , a [ i ] ∈ Z + (\sum_{i=1}^n a[i])=s,a[i]\in Z^+ (i=1na[i])=s,a[i]Z+
    问你是否对于所有的合法序列,都有某一个连续子段,满足这个子段的和为 k k k
  • 样例组数 ≤ 1 0 5 \le 10^5 105
    1 ≤ s , n , k ≤ 1 0 18 , n ≤ s 1\le s,n,k\le 10^{18},n\le s 1s,n,k1018,ns

思路

  • 首先容易得到 s = k s=k s=k 一定是 Y E S YES YES s < k s<k s<k 一定是 N O NO NO
    所以我们下文都考虑 s ≥ k s\ge k sk
    s t d std std 用到前缀和和抽屉原理的相关内容,我这里说明一个更好懂的做法。
  • 首先,若 s s s 最小,自然 a [ 1 ] = a [ 2 ] = ⋯ = a [ n ] = 1 a[1]=a[2]=\cdots =a[n]=1 a[1]=a[2]==a[n]=1,此时自然可以选择一个长度为 k k k 的子段,字段和就是 k k k 了。
  • 那么,若 s s s 非常大,自然一定有一个组合满足 a [ 1 ] , a [ 2 ] , ⋯ a [ n ] > k a[1],a[2],\cdots a[n]>k a[1],a[2],a[n]>k,自然是 N O NO NO
  • 然后,我们感觉当 s > = s>= s>= 某个值,就是 N O NO NO,否则就是 Y E S YES YES 的感觉。
  • 那么我们进行 极限构造,即用最小的 s s s 构造出一种不符合要求的组合,就是这个分界线了。
    我们怎么去构造呢?首先我想用很多个 1 1 1 填充,因为要求每个位置的值都是正整数。
    但是如果有连续的 k k k 1 1 1,那就合法了,不行,所以我们每 k − 1 k-1 k1 个数,就用一个数字间隔开。
    这个间隔的数字容易得到,至少是 k + 1 k+1 k+1
    所以我们构造出来的数组张这样:
    [ 1 , 1 , 1 , ⋯   , 1 k − 1 个 1 , ( k + 1 ) , 1 , 1 , 1 , ⋯   , 1 k − 1 个 1 , ( k + 1 ) , 1 , ⋯   , 1 ] [\underset{k-1个1}{1,1,1,\cdots,1},(k+1),\underset{k-1个1}{1,1,1,\cdots,1},(k+1),1,\cdots,1] [k111,1,1,,1,(k+1),k111,1,1,,1,(k+1),1,,1]
    那么,这个分界线容易得到,就是
    ⌊ n k ⌋ × ( k + 1 ) + ( n − ⌊ n k ⌋ ) \lfloor \frac{n}{k} \rfloor \times (k+1) + (n - \lfloor \frac{n}{k} \rfloor) kn×(k+1)+(nkn)
  • 容易得到,这个就是最少数目的 s s s,且非法的构造了。
    所以我们判断是否 s ≥ 这 个 玩 意 儿 s\ge 这个玩意儿 s,那就是 N O NO NO 了。

代码

  • 时间复杂度: O ( T ) O(T) O(T)
#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 = 3e5+50;
const int MOD = 1e9+7;

ll qpow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a%MOD;a=a*a%MOD;n>>=1;}return res;}
ll qpow(ll a,ll n,ll p){a%=p;ll res = 1LL;while(n){if(n&1)res=res*a%p;a=a*a%p;n>>=1;}return res;}
ll npow(ll a,ll n){/* */ll res = 1LL;while(n){if(n&1)res=res*a;a=a*a;n>>=1;if(res<0||a<0)return 0;}return res;}
ll inv(ll a){/* */return qpow(a,MOD-2);}
ll inv(ll a,ll p){return qpow(a,p-2,p);}

int main()
{
    int T;cin >> T;
    while(T--){
        ll s,n,k;
        cin >> s >> n >> k;
        if(s == k) puts("YES");
        else if(s < k) puts("NO");
        else{
            if(s >= n / k * (k + 1) + (n - n / k)) puts("NO");
            else puts("YES");
        }
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值