ACM训练第二周(摘自codeforces)

目录

A.    K-Complete Word(回文字符串)

用例输入 

题面翻译:

思路:

E.     Interesting Function(模拟)

用例输入

题面翻译:

代码实现

B.    Secret Passwords(并查集)

用例输入 

题面翻译

代码实现

D.  Cyclic Components (图论 / 并查集)

用例输入 

题面翻译

代码实现

图论实现:

并查集实现

C.  Edgy Trees(红黑树/并查集)

题意翻译

输入

代码实现 


A.    K-Complete Word(回文字符串)

训练的第一道签到题,写了半个小时,某场cf的div2 C题

Word s of length n is called k-complete if

  • s is a palindrome, i.e. si​=sn+1−i​ for all 1≤i≤n;
  • s has a period of k, i.e. si​=sk+i​ for all 1≤i≤n−k.

For example, "abaaba" is a 3-complete word, while "abccba" is not.

Bob is given a word s of length n consisting of only lowercase Latin letters and an integer k, such that n is divisible by k. He wants to convert s to any k-complete word.

To do this Bob can choose some i (1≤i≤n) and replace the letter at position i with some other lowercase Latin letter.

So now Bob wants to know the minimum number of letters he has to replace to convert s to any k-complete word.

Note that Bob can do zero changes if the word s is already k-complete.

You are required to answer t test cases independently.

输入描述

The first line contains a single integer t (1≤t≤105) — the number of test cases.

The first line of each test case contains two integers n and k (1≤k<n≤2⋅105, n is divisible by k).

The second line of each test case contains a word s of length n.

It is guaranteed that word s only contains lowercase Latin letters. And it is guaranteed that the sum of n over all test cases will not exceed 2⋅105.

输出描述

For each test case, output one integer, representing the minimum number of characters he has to replace to convert s to any k-complete word.

用例输入 

4
6 2
abaaba
6 3
abaaba
36 9
hippopotomonstrosesquippedaliophobia
21 7
wudixiaoxingxingheclp

用例输出 1 

2
0
23
16

提示

In the first test case, one optimal solution is aaaaaa.

In the second test case, the given word itself is k-complete.

题面翻译:

  • 给定一个长度为 n 的字符串 s 和一个参数 k,保证 k 整除 n 并且 s 中只含小写字母。
  • 请修改 s 中一些字符,使得修改后的字符串满足是以 k 为循环节长度的回文串。
  • 以 k 为循环节指对于所有的 1≤i≤n−k,s(i)​=s(i+k)​。
  • 求最少的修改次数。
  • 多组数据,n 之和不超过 2×10^5,k<n。

给你一个字符串,最少改变几个使其变成一个长度为n、周期为k的回文字符串

思路:

字符串可以拆成k个小字符串,每个小字符串也是回文的,小字符串的第i位和第k-1-i位是相同的
那么对于大字符串,它的第i+k*j位(j表示第几个小字符串)和第(k-1-i+k*j)位全部相等,并且与其它位是什么没有关系(尤其重要)

然后我们就可以分别处理每一个相互对应位置上的字符,将他们统一变成在这个位置上出现次数最多的字母,将变化次数求和即可AC

每一位上变化次数为:总字符数-出现最多字母的出现次数
小回文串中出现两次的位置个数为:2*n/k
只出现一次的位置个数为:(k%2==1)*n*k

#include<bits/stdc++.h>
using namespace std;
int cnt[100];
bool cmp(int a,int b){  //降序排出现次数
    return a>b;
}
int main(){
    int T;
    cin>>T;     //数据组数
    while(T--){
        int n,k;
        cin>>n>>k;
        int t=n/k;      //小字符串个数
        string s;
        cin>>s;
        int ans=0;
        if(k%2==1){
            for(int i=0;i<k/2;i++){
                memset(cnt,0,sizeof(cnt));      //初始为0
                for(int j=0;j<t;j++){
                    cnt[s[i+j*k]-'a']++;        //对每个字母出现次数计数
                    cnt[s[k-1-i+j*k]-'a']++;    //回文位置
                }
                sort(cnt,cnt+26,cmp);       //找出对应位置上出现次数最多的字母
                ans+=2*t-cnt[0];
            }
            memset(cnt,0,sizeof(cnt));
            int i=k/2;
            for(int j=0;j<t;j++){
                cnt[s[i+k*j]-'a']++;      //小回文串长度为奇数,额外加上中间的位置
            }
            sort(cnt,cnt+26,cmp);
            ans+=t-cnt[0];
        }
        else{
            for(int i=0;i<k/2;i++){     //偶数直接copy
                memset(cnt,0,sizeof(cnt));
                for(int j=0;j<t;j++){
                    cnt[s[i+j*k]-'a']++;
                    cnt[s[k-1-i+j*k]-'a']++;
                }
                sort(cnt,cnt+26,cmp);
                ans+=2*t-cnt[0];
            }
        }
        cout<<ans<<endl;
    }
    return 0;
}

E.     Interesting Function(模拟)

You are given two integers l and r, where l<r. We will add 1 to l until the result is equal to r. Thus, there will be exactly r−l additions performed. For each such addition, let's look at the number of digits that will be changed after it.

For example:

  • if l=909, then adding one will result in 910 and 2 digits will be changed;
  • if you add one to l=9, the result will be 10 and 2 digits will also be changed;
  • if you add one to l=489999, the result will be 490000 and 5 digits will be changed.

Changed digits always form a suffix of the result written in the decimal system.

Output the total number of changed digits, if you want to get r from l, adding 1 each time.

输入描述

The first line contains an integer t (1≤t≤10^4). Then t test cases follow.

Each test case is characterized by two integers l and r (1≤l<r≤10^9).

输出描述

For each test case, calculate the total number of changed digits if you want to get r from l, adding one each time.

用例输入

4
1 9
9 10
10 20
1 1000000000

用例输出 1 

8
2
11
1111111110

题面翻译:

给定两个正整数 l,r(l<r),将 l 不断加 1 直到 l=r,求出这一过程中 l 发生变化的位数总数

位数变化指:

  • l=909,将 l+1 后有 22 位数字发生变化。
  • l=9,将 l+1 后也有 22 位数字发生变化。
  • l=489999,将 l+1 后有 55 位数字发生变化。

而总数指:

  • l=10,r=20,个位变化了 1010 次,十位变化了 11 次,所以总数为 1111。

代码实现

 比A题更签,纯模拟,发现从1改变到n,个位数贡献1的改变,十位数贡献11的改变,百位数贡献111,以此类推
用1->r,1->l的次数相减,即是答案

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll cnt[20]={1,11,111,1111,11111,111111,1111111,11111111,111111111,1111111111};
                    //打表直接代
ll cal(ll n){
    ll ans=0;
    int t=0;
    while(n){
        ans+=(n%10)*cnt[t++];
        n/=10;
    }
    return ans;
}
int main(){
    int T;
    cin>>T;
    while(T--){
        ll l,r;
        cin>>l>>r;
        ll ans=cal(r)-cal(l);
        cout<<ans<<endl;
    }
    return 0;
}

B.    Secret Passwords(并查集)

某场cf div2 的D题 ,学了并查集再来补一遍

One unknown hacker wants to get the admin's password of AtForces testing system, to get problems from the next contest. To achieve that, he sneaked into the administrator's office and stole a piece of paper with a list of n passwords — strings, consists of small Latin letters.

Hacker went home and started preparing to hack AtForces. He found that the system contains only passwords from the stolen list and that the system determines the equivalence of the passwords a and b as follows:

  • two passwords a and b are equivalent if there is a letter, that exists in both a and b;
  • two passwords a and b are equivalent if there is a password c from the list, which is equivalent to both a and b.

If a password is set in the system and an equivalent one is applied to access the system, then the user is accessed into the system.

For example, if the list contain passwords "a", "b", "ab", "d", then passwords "a", "b", "ab" are equivalent to each other, but the password "d" is not equivalent to any other password from list. In other words, if:

  • admin's password is "b", then you can access to system by using any of this passwords: "a", "b", "ab";
  • admin's password is "d", then you can access to system by using only "d".

Only one password from the list is the admin's password from the testing system. Help hacker to calculate the minimal number of passwords, required to guaranteed access to the system. Keep in mind that the hacker does not know which password is set in the system.

输入描述

The first line contain integer n (1≤n≤2⋅10^5) — number of passwords in the list. Next n lines contains passwords from the list – non-empty strings si​, with length at most 50 letters. Some of the passwords may be equal.

It is guaranteed that the total length of all passwords does not exceed 10^6 letters. All of them consist only of lowercase Latin letters.

输出描述

In a single line print the minimal number of passwords, the use of which will allow guaranteed to access the system.

用例输入 

4
a
b
ab
d

用例输出 1 

2

用例输入 2 

3
ab
bc
abc

用例输出 2 

1

用例输入 3 

1
codeforces

用例输出 3 

1

提示

In the second example hacker need to use any of the passwords to access the system.

题面翻译

给你一串密码,两个有相同字符的密码是等价的,与同一个密码等价的多个密码是等价的
问:这串密码共有多少种等价密码

正解应该是并查集,但是现在还没学会,写了很复杂的集合操作,调了一个多小时,最后也A掉了

代码实现

我的思路:先一个个读入密码,如果和之前的密码有交叉,就合并进去看成一个密码,否则另开一个集合存当前密码
由于最后的密码可能出现重复,如{a,b,ab}-->{ab,a},最后再将所有有交叉的密码合并,即可通过。

补:正解思路:
将每一组密码的首字母标记为根节点,将所有密码合并
然后搜一遍储存的根节点,最终集合合并后的根节点数为答案

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
    int T;
    cin>>T;
    set<char>s[30];     //一共26个字符,最多26个密码,所以后续的循环遍历不会TLE
    int num=0;      //当前的密码个数
    while(T--){
        string str;
        cin>>str;
        if(num==0){     //将第一个密码全部插入s[0]
            num=1;
            for(int i=0;i<str.length();i++){
                s[0].insert(str[i]);
            }
            continue;
        }
        
        bool flag=0;       //记录当前密码在之前是否出现过
        for(int i=0;i<num;i++){     //遍历之前所有密码
            for(int j=0;j<str.length();j++){    //遍历当前密码的每一位
                if(s[i].find(str[j])!=s[i].end()){
                    flag=1;     //找见等价密码,打上标记
                    break;
                }
            }
            if(flag){       //把当前密码合并到等价密码
                for(int j=0;j<str.length();j++){
                    s[i].insert(str[j]);
                }
                break;
            }
        }
        if(flag==0){        //没找到等价密码,先另开一个集合存当前密码
            num++;
            for(int i=0;i<str.length();i++){
                s[num-1].insert(str[i]);
            }
        }
    }
    int ans=num;        //ans标记最终完全不重复的密码
    for(int i=0;i<num-1;i++){
        for(int j=i+1;j<num;j++){   //对于每个密码,和其它密码对比有无交叉
            bool flag=0;
            for(set<char>::iterator it=s[i].begin();it!=s[i].end();it++){
                if(s[j].find(*it)!=s[j].end()){
                    flag=1;     //有交叉,打上标记
                    break;
                }
            } 
            if(flag==1){        //把两个交叉的密码合并,总密码数-1
                ans--;
                for(set<char>::iterator it=s[i].begin();it!=s[i].end();it++){
                    s[j].insert(*it);
                }
                s[i].clear();
                break;
            }
        }
    }
    cout<<ans<<endl;
    return 0;
}

 浅浅学了点并查集,看了看题解,补一个并查集的正解

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int f[105];     //并查集储存节点的父节点
int find(int i){
    while(f[i]!=i)
        i=f[i];
    return i;
}
bool fflag[50]; //是否是根节点
void unit(){        //初始化
    for(int i=0;i<30;i++)f[i]=i;
    return;
}
bool node[50];
int main(){
    int T;
    cin>>T;
    unit();
    while(T--){
        string str;
        cin>>str;
        fflag[str[0]-'a']=1;    //先使每个密码的首字符为祖先
        for(int i=1;i<str.length();i++){
            f[find(str[i]-'a')]=f[str[0]-'a'];  //合并(即将该字符的根节点设为首字符的祖先节点)
        }
    }
    int ans=0;
    for(int i=0;i<30;i++){
        if(fflag[i]){   //判断每个根节点
            if(!node[find(f[i])]){      //如果根节点没出现过,标记,集合个数+1
                node[find(f[i])]=1;
                ans++;
            }
        }
    }
    cout<<ans;
    return 0;
}


D.  Cyclic Components (图论 / 并查集)

You are given an undirected graph consisting of n vertices and m edges. Your task is to find the number of connected components which are cycles.

Here are some definitions of graph theory.

An undirected graph consists of two sets: set of nodes (called vertices) and set of edges. Each edge connects a pair of vertices. All edges are bidirectional (i.e. if a vertex a is connected with a vertex b, a vertex b is also connected with a vertex a). An edge can't connect vertex with itself, there is at most one edge between a pair of vertices.

Two vertices u and v belong to the same connected component if and only if there is at least one path along edges connecting u and v.

A connected component is a cycle if and only if its vertices can be reordered in such a way that:

  • the first vertex is connected with the second vertex by an edge,
  • the second vertex is connected with the third vertex by an edge,
  • ...
  • the last vertex is connected with the first vertex by an edge,
  • all the described edges of a cycle are distinct.

A cycle doesn't contain any other edges except described above. By definition any cycle contains three or more vertices.

There are 6 connected components, 2 of them are cycles: [7,10,16] and [5,11,9,15].

输入描述

The first line contains two integer numbers n and m (1≤n≤2⋅105, 0≤m≤2⋅105) — number of vertices and edges.

The following m lines contains edges: edge i is given as a pair of vertices vi​, ui​ (1≤vi​,ui​≤n, ui​=vi​). There is no multiple edges in the given graph, i.e. for each pair (vi​,ui​) there no other pairs (vi​,ui​) and (ui​,vi​) in the list of edges.

输出描述

Print one integer — the number of connected components which are also cycles.

用例输入 1 

5 4
1 2
3 4
5 4
3 5

用例输出 1 

1

用例输入 2 

17 15
1 8
1 12
5 11
11 9
9 15
15 5
4 13
3 13
4 3
10 16
7 10
16 7
14 3
14 4
17 6

用例输出 2 

2

提示

In the first example only component [3,4,5] is also a cycle.

The illustration above corresponds to the second example.

题面翻译

给定一张 n 个点,m 条边的无向图。保证无重边、无自环。在该图的所有连通块中,你需要找出环的个数。 

无向图的环的定义如下:

原无向图中的一个子图被定义为环,当且仅当它的点集重新排序后可以满足如下条件:

  • 第一个点与第二个点通过一条边相连接;
  • 第二个点与第三个点通过一条边相连接;
  • ……
  • 最后一个点与第一个点通过一条边相连接。
  • 所有的边都应当是不同的。
  • 其边集不应当包含除了以上所述的边以外的任何边。

这样,我们就称这个子图(点 + 边)为环。

根据定义,一个环至少需要包含三个点,且边数与点数应当是相同的。

例如对于上图,共有 66 个联通块,但只有 [7,10,16][7,10,16] 和 [5,11,9,15][5,11,9,15] 这两个联通块是环。

代码实现

自己没写出来,以为只能写图论,看了看题解,发现还能写并查集,来补个题

图论实现:

使用vector存图,符合条件的连通块每个点有两条边,把图深搜一遍,判掉不符条件的联通块即可即可
(本人第一道图论题)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
bool vis[200005];           //记录是否被搜过
vector<int>v[200005];       //使用数组存图,v[i]储存和 i 相连的点
bool flag;
void dfs(int i){
    if(v[i].size()!=2){     //相连的点不为2,不符题意
        flag=0;
        return;
    }
    if(vis[i])return;       //搜过即返回
    vis[i]=1;       //标记
    for(int j=0;j<v[i].size();j++){
        dfs(v[i][j]);       //搜和它相连的点
    }
}
int main(){
    int n,m;
    cin>>n>>m;
    while(m--){
        int a,b;
        cin>>a>>b;
        v[a].push_back(b);  //储存无向边的两个端点
        v[b].push_back(a);
    }
    ll ans=0;
    for(int i=1;i<=n;i++){
        if(!vis[i]){    
            flag=1;         //判断是否是合法连通块
            dfs(i);     
            if(flag)ans++;
        }
    }
    cout<<ans;
    return 0;
}

并查集实现

将连在一起的点利用并查集合并,通过边数判断连通块是否合法,然后统计合法的连通块个数(好简单的样子,但是竟然没想出来??)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int f[200005];
int h[200005];
void init(){    //并查集模板
    for(int i=0;i<200005;i++){
        f[i]=i;
        h[i]=0;
    }
}
int Find(int i){    //并查集模板
    int r=i;
    while(r!=f[r])r=f[r];
    while(i!=f[i]){
        int t=f[i];
        f[i]=r;
        i=t;
    }
    return r;
}
void Merge(int x,int y){    //并查集模板
    x=Find(x);
    y=Find(y);
    if(h[x]==h[y]){
        h[y]++;
        f[x]=y;
    }
    else{
        if(h[x]<h[y])f[x]=y;
        else f[y]=x;
    }
}
int cnt[200005];    //记录每个点的边数
bool visit[200005];     //判断是否符合题意
int main(){
    init();
    int m,n;
    cin>>n>>m;
    while(m--){
        int u,v;
        cin>>u>>v;
        Merge(u,v);     //合并
        cnt[u]++;       //记录边数
        cnt[v]++;
    }
    for(int i=1;i<=n;i++){      //合法连通块所有点的边数都应该为2
        if(cnt[i]!=2)visit[Find(i)]=1;
    }
    ll ans=0;
    for(int i=1;i<=n;i++){
        if(Find(i)==i&&visit[i]==0)ans++;   //搜索根节点,判断是否合法
    }
    cout<<ans;
    return 0;
}

C.  Edgy Trees(红黑树/并查集)

题目描述

You are given a tree (a connected undirected graph without cycles) of n vertices. Each of the n−1 edges of the tree is colored in either black or red.

You are also given an integer k . Consider sequences of k vertices. Let's call a sequence[a1​,a2​,…,ak​] good if it satisfies the following criterion:

  • We will walk a path (possibly visiting same edge/vertex multiple times) on the tree, starting from a1​ and ending at ak​ .
  • Start ata1​ , then go to a2​ using the shortest path between a1​ and a2​ , then go to a3​ in a similar way, and so on, until you travel the shortest path between ak−1​ and ak​ .
  • If you walked over at least one black edge during this process, then the sequence is good.

Consider the tree on the picture. If k=3 then the following sequences are good: [1,4,7] ,[5,5,3] and [2,3,7] . The following sequences are not good: [1,4,6] ,[5,5,5] , [3,7,3]

There are nk sequences of vertices, count how many of them are good. Since this number can be quite large, print it modulo 10^9+7

输入格式

The first line contains two integers n and k ( 2≤n≤10^5 , 2≤k≤100 ), the size of the tree and the length of the vertex sequence.

Each of the next n−1 lines contains three integers ui​ , vi​ and xi​ ( 1≤ui​,vi​≤n , xi​∈{0,1} ), where ui​ and vi​ denote the endpoints of the corresponding edge and xi​ is the color of this edge ( 00 denotes red edge and 11 denotes black edge).

输出格式

Print the number of good sequences modulo 109+7

题意翻译

给定一棵包含 n 个顶点的无根树,每条边的颜色为红或黑。

我们将满足下列条件的序列称为“好序列”:包含 k 个顶点,在从 a1​ 走到 ak​ 的过程中走过了至少一条黑边。其中从 a1​ 到 a2​ 走的是它们之间的最短路径,从 a2​ 到 a3​ 走的也是它们之间的最短路径,以此类推。你可以多次经过一个点。

给定 n,k 和 n−1 行 u,v,w 表明一条边的两个顶点以及它的颜色(0 表示红,1 表示黑)。

输出好序列的个数,答案对 10^9+7 取模。

输入

4 4
1 2 1
2 3 1
3 4 1

输出

252

输入2

4 6
1 2 0
1 3 0
1 4 0

输出 2

0

输入 3

3 5
1 2 1
2 3 0

输出 3

210

说明/提示

In the first example, all sequences ( 4^4 ) of length 4 except the following are good:

  • [1,1,1,1]
  • [2,2,2,2]
  • [3,3,3,3]
  • [4,4,4,4]

In the second example, all edges are red, hence there aren't any good sequences.

思路:

所有序列的个数为n^k个,图被红色的边分为若干个联通块,只由同一个连通块中的点形成的序列一定不经过黑色边,分布在不同连通块的序列一定经过红色边。
有n个点的连通块可以形成n^k个序列,总序列个数减去连通块形成序列的个数即为答案
连通块的形成使用并查集

代码实现 

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int f[100005];
int h[100005];
void init(){        //并查集模板
    for(int i=0;i<100003;i++){
        f[i]=i;
        h[i]=0;
    }
}
int Find(int i){       //并查集模板
    int r=i;
    while(r!=f[r])r=f[r];
    while(i!=f[i]){
        int t=f[i];
        f[i]=r;
        i=t;
    }
    return r;
}
void Merge(int x,int y){    //并查集模板
    x=Find(x);
    y=Find(y);
    if(h[x]==h[y]){
        h[y]++;
        f[x]=y;
    }
    else{
        if(h[x]<h[y])f[x]=y;
        else f[y]=x;
    }
}
const ll mod=1e9+7;
ll ksm(ll base,ll power){    //快速幂取模
	ll ans=1;
	while(power>0){
		if(power&1)
			ans=(ans*base)%mod;
		base=(base*base)%mod;
		power>>=1;
	}
	return ans;
}
ll cnt[100005];
int main(){
    ll n,k;
    cin>>n>>k;
    init();
    ll ans=0;
    for(int i=1;i<n;i++){
        int u,v,flag;
        cin>>u>>v>>flag;
        if(flag==0){        //用红色边形成连通块
            Merge(u,v);
        }
    }
    for(int i=1;i<=n;i++){
        cnt[Find(i)]++;        //计录每个连通块内的点数
    }
    for(int i=1;i<=n;i++){
        if(Find(i)==i)ans=(ans+ksm(cnt[i],k))%mod;
    }        //计算经过红色边的序列总数
    cout<<(ksm(n,k)+mod-ans)%mod;    //防止负数,加个mod
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Auroraaaaaaaaaaaaa

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值