CUSTACM Summer Camp 2022 Training 5
bc重复,h题规律题没写题解
A. Even Picture
题意
构造图形,要求染色的格子彼此连通,每个格子都有偶数个邻居,其中有且只有n个格子,这n个格子中的每个格子有4个邻居,输出这些格子的位置坐标。
数据范围: 1 ≤ n ≤ 500 1 \leq n \leq 500 1≤n≤500
tags: 构造,找规律
思路
从n=1开始模拟,就可以发现构造方法了
代码
#include<iostream>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n;
cin>>n;
cout<<3*n+4<<endl;
cout<<0<<' '<<0<<endl;
cout<<1<<' '<<0<<endl;
int x=0;
for(int i=1;i<=n;i++){
cout<<x<<' '<<i<<endl;
cout<<x+1<<' '<<i<<endl;
cout<<x+2<<' '<<i<<endl;
x++;
}
cout<<n<<' '<<n+1<<endl;
cout<<n+1<<' '<<n+1<<endl;
}
复杂度
O ( n ) O(n) O(n)
B. Codeforces Subsequences
题意
构造一个字符串,使构造出的字符串中“codeforces”字串(该字串可以是不连续的)的数量大于等于k,求满足条件的字符串的最小长度
数据范围: 1 ≤ k ≤ 1 0 16 1 \leq k \leq 10^{16} 1≤k≤1016
tags:构造,思维
思路
问题就是如何向codeforces中插入x个字符使出现的字串数量m最大
x=1,无论放哪都行
x=2,最优策略是两个字符不同且位置不连续,如ccoodeforces,m=22
x=3,最优策略是三个字符且位置不连续,如ccooddeforces,m=23
……
x=11,最优策略是2个相同字符,9个不同字符,如cccooddeeffoorrcceess,m=29*31
尽可能去实现乘法,按照上述规律x从1开始枚举,直到m大于1016次方就行,大约x到150就可以了
然后用二分找到第一个大于等于k的数对应的字符数量x,然后按照上述构造规则输出即可
代码中有很多细节讲不清,按自己的思路写吧
代码
#include<iostream>
#include<cmath>
#include<algorithm>
#define ll long long
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
ll a[1000]={1};
ll x=2,y=1,n;
for(ll i=1;i<=500;i++,y++){//枚举
a[i]=pow(x,y)*pow(x-1,10-y);
// cout<<x<<' '<<y<<endl;
if(i%10==0)x++,y=0;
if(a[i]>=1e16){
n=i;
break;
}
}
// for(int i=1;i<=90;i++)cout<<a[i]<<endl;
ll k;cin>>k;
int number=lower_bound(a,a+n,k)-a;
int n1=number/10+1,n2=number%10;
string s="*codeforces";
for(int i=1;i<=n2;i++){
for(int j=1;j<=n1+1;j++)cout<<s[i];
}
for(int i=n2+1;i<=10;i++){
for(int j=1;j<=n1;j++)cout<<s[i];
}
}
复杂度
很小,几乎为 O ( 1 ) O(1) O(1)了,因为字符串长度最大也为150左右
D. Ehab and Prefix MEXs
题意
首先有个定义, 给定一个数组, MEX(i)表示数组前i项没有出现过的最小自然数.
要求就是给你一个数组a, 让你构建一个数组b, 要求对于b数组而言, MEX(i)的值为a[i]. 如果无法构建这样的数组, 输出-1;
0 ≤ a i ≤ i , 保证 a i ≤ a i + 1 0 \leq a_i \leq i,保证a_i \leq a_{i+1} 0≤ai≤i,保证ai≤ai+1
数据大小: 0 ≤ b i ≤ 1 0 6 0 \leq b_i \leq 10^6 0≤bi≤106
思路
在第i个位置a数组的值为ai,则需要b数组在前i个数中出现0到ai-1(共ai个数)的所有数
-
所以当ai>i时,b数组中前i个数放不齐要求的ai个数,一定为-1。但是题目中ai<=i,所以一定是可以构造的
-
a i ≠ a i − 1 a_i \neq a_{i-1} ai=ai−1时
- 首先要求b中前i个数中有0到ai-1这些数,有数ai-1
- 又要求b中前i-1个数中有0到ai-1-1这些数,且没有数ai-1
- 所以ai-1必定出现在bi处
-
a i − 1 = = a i a_{i-1}== a_i ai−1==ai时
-
要注意到ai时不下降的,且 0 ≤ a i ≤ i 0 \leq a_i \leq i 0≤ai≤i,所以理想状态应该是0,1,2,3…或1,2,3,4…,但如果出现 a i − 1 = = a i a_{i-1}==a_i ai−1==ai那么前n个数中会缺少某一个数x,此时我们需要把缺少的数x放到bi处,防止这个在a中缺少的数对后面计算MEX产生影响(因为如果不把x加入b中,后续某个MEX(i)可能就是x,但它未在a中出现过)
有点难理解,看图吧:
-
代码
#include<iostream>
using namespace std;
const int maxn=1e5+5;
int n;
int a[maxn],vis[maxn],b[maxn];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
vis[a[i]]=1;//记录已经出现过的数
}
int cnt=0;
for(int i=0;i<=n;i++){
if(!vis[i])b[cnt++]=i;//记录没有出现过的数
}
cnt=0;
a[0]=a[1];
for(int i=1;i<=n;i++){
if(a[i]!=a[i-1])cout<<a[i-1]<<' ';
else cout<<b[cnt++]<<' ';
}
}
复杂度
O ( n ) O(n) O(n)
E. Game On Leaves
题意
一颗n个节点的数,两个人分别删除叶节点,最后删掉x节点的人获胜。
数据大小: 1 ≤ t ≤ 10 , 1 ≤ n ≤ 1000 1 \leq t \leq 10,1 \leq n \leq 1000 1≤t≤10,1≤n≤1000
tags:(树上)博弈
思路
-
若x就是一个叶节点(度为1),那么先手必胜
-
若x不是一个叶节点,那么当x的度变为1时就会确定胜者
-
当删到x的度为2时,如
没有人会主动去删节点1,不然对手就胜了,每个人都会去删其他其他叶子节点直到x度为1,也就是只剩2个节点,所以我们只需判断n-2的奇偶性就行了
-
注意:度可以为0!
代码
#include <iostream>
#include<cstring>
using namespace std;
const int maxn = 1005;
int n, k;
int d[maxn];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t;
for (cin >> t; t; t--)
{
memset(d,0,sizeof(d));
cin >> n >> k;
for (int i = 0; i < n-1; i++)
{
int x, y;
cin >> x >> y;
d[x]++, d[y]++;
}
if (d[k]<=1)
cout << "Ayush" << endl;
else
cout << (((n - 2) & 1) ? "Ashish" : "Ayush") << endl;
}
}
复杂度
O ( n ) O(n) O(n)
F. AND, OR and square sum位运算好题
题意
给你n个数,对于i!=j,a[i]=x,a[j]=y,我们可以进行一种操作:a[i]=x&y,a[j]=x|y,你可以无限的进行这个操作,问a[i]2的和为最大为多少
数据范围: 1 ≤ n ≤ 2 ∗ 1 0 5 , 0 ≤ a i ≤ 2 20 1 \leq n \leq 2*10^5,0 \leq a_i \leq 2^{20} 1≤n≤2∗105,0≤ai≤220
tags:位运算,思维
思路
a i 和 a j 某一二进制位 a_i和a_j某一二进制位 ai和aj某一二进制位 | 1和1 | 1和0 | 0和1 | 0和0 |
---|---|---|---|---|
&运算 | 1 | 0 | 0 | 0 |
|运算 | 1 | 1 | 1 | 0 |
a i 和 a j 中在该二进制位上 1 的个数 a_i和a_j中在该二进制位上1的个数 ai和aj中在该二进制位上1的个数 | 2个1 | 1个1 | 1个1 | 0个1 |
可以发现上述操作不会改变任意一二进制位上1的个数
所以就尽可能把所有的1都放在一个数的二进制位中,这样可以实现最后平方和最大
代码
#include<iostream>
#define ll long long
using namespace std;
const int maxn=2e5+5;
int n;
int count[32];
void number1(int x){//提取二进制位上的1
for(int i=0;i<=21;i++){
if(x&(1<<i))count[i]++;
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=0;i<n;i++){
int x;cin>>x;
number1(x);
}
// for(int i=0;i<=21;i++)cout<<count[i]<<' ';
// cout<<endl;
ll sum=0;
while(1){
bool flag=false;
ll x=0;
for(int i=0;i<=21;i++){
if(count[i]){
x+=(1<<i);
count[i]--;
flag=true;
}
}
if(!flag)break;
sum+=x*x;
}
cout<<sum<<endl;
}
复杂度
o ( n ) o(n) o(n)
G. Johnny and Contribution
题意
给定n个点,m条边。每个点都有一个权值 t i t_i ti。
问你如何遍历图,使得当前节点为u,与u相连的节点集合{v1,v2,v3,v4,v5…}求出MEX{v1,v2,v3,v4…v5} 为 x 判断 a[u]是否等于x 。
如果存在序列输出,如果不存在输出-1
数据范围: 1 ≤ n ≤ 5 ∗ 1 0 5 , 0 ≤ m ≤ 5 ∗ 1 0 5 , 1 ≤ t i ≤ n 1 \leq n \leq 5*10^5,0 \leq m \leq 5*10^5,1 \leq t_i \leq n 1≤n≤5∗105,0≤m≤5∗105,1≤ti≤n
tags:思维
思路
当一个点的权值为ti时,要求遍历它时它的已经遍历的邻居有1到ti-1的所有权值,所以答案肯定是从权值小的点开始遍历
而如何判断会不会符合MEX的要求呢,我们只需保证点u的所有邻居**集合v中有1到ti-1的所有数值且没有ti**即可,因为我们从权值小的点开始遍历,所以那些点肯定是已经遍历过的,只要保证”有“那些点就符合条件
代码
#include<iostream>
#include<map>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn=5e5+5;
int n,m;
struct node{
int num,b;
bool operator < (node&x){
return num<x.num;
}
}a[maxn];
vector<int>edge[maxn];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=0;i<m;i++){
int x,y;
cin>>x>>y;
edge[x].push_back(y);
edge[y].push_back(x);//无向图
}
for(int i=1;i<=n;i++){
cin>>a[i].num;
a[i].b=i;
}
for(int i=1;i<=n;i++){
map<int,int>judge;//用map来存已经在集合v中的权值(一开始我用数组存,会超时哦)
int st=a[i].b,sco=a[i].num;
for(auto v:edge[st]){
judge[a[v].num]=true;
}
if(judge[sco]){//如果有于u权值相同的点,不符合条件
cout<<-1<<endl;
return 0;
}
for(int i=1;i<sco;i++){//判断从1到sco-1的值是否都有
if(!judge[i]){
cout<<-1;
return 0;
}
}
}
sort(a+1,a+1+n);//最后排序输出即可
for(int i=1;i<=n;i++)cout<<a[i].b<<' ';
}
复杂度
按理说 O ( n 2 ) O(n^2) O(n2),因为遍历了从1到ti-1,而ti范围最大又是n。但是过了,有点奇怪,可能数据中ti没有那么大❓❓
I. K-Complete Word字符匹配好题
题意
给你一个长度为n字符串, 让你求出最少修改多少次字母,才能使字符串满足周期为k,且字符串为回文串。保证n能被k整除
数据范围: 1 ≤ k , n ≤ 2 ∗ 1 0 5 1 \leq k,n \leq 2*10^5 1≤k,n≤2∗105
tags:思维,字符串匹配
思路
周期为k又要求是回文字符串,可以分析出它在每一个周期中都是相同的回文字符串,我们只需统计每个周期中字符(是每个小周期中回文字符串回文处对应位置的两个字符)出现的次数,取出现次数最多的字符,把其他字符变为它即可
代码有很多细节,按自己的思路实现就好
代码
#include<iostream>
using namespace std;
const int maxn=2e5+5;
int n,k;
string s;
int cnt[maxn];
string str[maxn];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t;
for(cin>>t;t;t--){
cin>>n>>k>>s;
s="*"+s;
int div=n/k;//有n/k个小回文字符串
int ans=0;
for(int i=1;i<=(k+1)/2;i++){//小回文字符串长度为k,需要统计的位置有(k+1)/2个(就是回文位置有(k+1)/2对)
int cnt[300]={0};
for(int j=1;j<=div;j++){//对个每个周期的字符串统计该回文位置出现的字符次数
// cout<<(j-1)*k+i<<' '<<j*k-i+1<<"&&&&&"<<endl;
cnt[s[(j-1)*k+i]]++;
if((j*k-i+1)!=((j-1)*k+i))cnt[s[j*k-i+1]]++;//(j-1)*k+i是前面的位置,j*k-i+1是后面的位置,两位置为回文对应位置
}
int m=0;
for(int l='a';l<='z';l++)m=max(m,cnt[l]);//字符出现最多的来选来组成回文字符串
// cout<<div<<' '<<m<<"**"<<endl;
if((k&1)&&i==(k+1)/2)ans+=div-m;//注意如果为k为奇数,回文字符串中间是只有1个字符的,是用div-m
else ans+=2*div-m;
}
cout<<ans<<endl;
}
}
复杂度
O ( n ) O(n) O(n)
J. Navigation System最短路径好题
题意
给你一个有向图,和一个既定的序列 a ,我们沿着这个既定的序列走,在每个位置,导航会重新规划路线 (重新规划的路线一定是到终点的最短路),路线可能和原路线重合也可能和原路线不同,如果不同即是重建,问可能的最小和最大重建次数
数据范围: 2 ≤ n , m ≤ 2 ∗ 1 0 5 , 2 ≤ k ≤ n 2 \leq n,m \leq 2*10^5,2 \leq k \leq n 2≤n,m≤2∗105,2≤k≤n
tags:最短路径,思维
思路
先求出终点v到每个点的最短路径(因为是有向图,该过程需要反向建图),然后在从u到v的给定路径来判断
- 若dis[u]!=dis[v]+1说明v一定不在最短路径上,所以到了v点导航一定会重新规划路径
- 若dis[u]==dis[v]+1,说明v在最短路径上,在寻找有没有V点使dis[u]=dis[V],如果有则导航给定最短路径是V到终点,而不是v到终点,这时可能不重建,可能重建(最小值不变,最大值+1)
- 上述过程需要正向的图,不然u的对应点v会弄错
我求最短路径是直接用Dijkstra+优先队列
代码
#include <iostream>
#include <queue>
#include <algorithm>
#include <vector>
#include <utility>
using namespace std;
typedef pair<int, int> P; // first为距离,second为端点
const int maxn = 2e5 + 10, maxm = 2e5 + 10, inf = 1e9 + 1;
int n, m;
struct edge
{
int to, cost;
};
vector<edge> g[maxm],gg[maxm];
int dis[maxn];
int a[maxn];
void dijkstra(int s)//dijkstra+优先队列优化求多源最短路径
{
priority_queue<P, vector<P>, greater<P>> q;
fill(dis, dis + 1 + n, inf);
dis[s] = 0;
q.push(P(0, s));
while (!q.empty())
{
P st = q.top();
q.pop();
int pos = st.second;
if (dis[pos] < st.first)
continue;
for (int i = 0; i < g[pos].size(); i++)//反向的图
{
edge x = g[pos][i];
if (dis[x.to] > dis[pos] + x.cost)
{
dis[x.to] = dis[pos] + x.cost;
q.push(P(dis[x.to], x.to));
}
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin >> n >> m;
for (int i = 0; i < m; i++)
{
int u, v;
cin >> u >> v;
g[v].push_back(edge{u, 1});//反向的图
gg[u].push_back(edge{v,1});//正向的图
}
int k;
cin >> k;
for (int i = 1; i <= k; i++)
cin >> a[i];
dijkstra(a[k]);
// for (int i = 1; i <= k; i++)
// cout << dis[i] << ' ';
// cout << endl;
int mn = 0, mx = 0;
for (int i = 1; i < k; i++)
{
int u = a[i], v = a[i + 1];
if (dis[u] != dis[v] + 1)
mn++, mx++;
else
{
for (int j = 0; j < gg[u].size(); j++)//正向的图
{
int to = gg[u][j].to;
if (dis[to] == dis[u] - 1 && to != v)//注意to!=v,要另一个最短路径,且只要出现了就可以break
{
mx++;
break;
}
}
}
}
cout << mn << ' ' << mx << endl;
}
复杂度
主要在求最短路径 O ( m l o g n ) O(mlogn) O(mlogn)