ZJNU 2021-07-13 个人排位赛2 部分题解
写的一手烂代码.jpg
写得比较匆忙,本文中可能会出现某些错字,按读音理解即可(
另一篇:https://blog.csdn.net/weixin_45775438/article/details/118707347
A - Minimum Cost Paths
防AK 😦
B - Uddered but not Herd I
记录每个字母的顺序,给定一个id
其后遍历给定的字符串每一对相邻的字母,如果后一个字母的id顺序在前一个字母之前(包括相等),则答案需要+1
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
int id[30];
void solve()
{
string s;
string t;
cin>>s>>t;
repp(i,0,26)
id[s[i]-'a']=i;
int ans=1;
int siz=t.size();
repp(i,1,siz)
{
if(id[t[i]-'a']<=id[t[i-1]-'a'])
ans++;
}
cout<<ans<<'\n';
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
C - Telephone
题意
\(n\)头牛站成一排,每头牛都有对应的种类\(k\),种类数最多\(50\)种
给定的矩阵\(s_{i,j}\)表示第\(i\)种牛是否可以向第\(j\)种牛传话
现在位置为\(1\)的牛想向位置为\(n\)的牛传话,问传话的最小花费,不存在传话方案则输出\(-1\)
两头位置分别为\(x,y\)的牛进行传话,花费为\(|x-y|\)
思路
考虑记录每种种类的牛的位置,存在\(vector<int> POS[]\)中
再记录每种种类可以传话的后置种类(建成一张有向图),存在 vector
跑dijkstra最短路,每次对于当前处理的牛,遍历其种类能够传话的后置种类,再遍历这个后置种类中所有牛的位置
这样的双重遍历肯定是超时的,但大胆猜测,对于每种种类,其最多被遍历\(50\)次,通过数组计数限制后即可
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
vector<int> G[55];
vector<int> POS[55];
int n,k;
int kind[50050];
ll dis[50050];
int vis[55];
void solve()
{
cin>>n>>k;
rep(i,1,n)
{
cin>>kind[i];
POS[kind[i]].pb(i);
}
rep(i,1,k)
{
string s; cin>>s;
repp(j,0,k)
if(s[j]=='1')
G[i].pb(j+1);
}
mst(dis,LINF);
priority_queue<P> q;
dis[1]=0;
q.push(P(0,1));
while(!q.empty())
{
P pd=q.top();
q.pop();
int u=pd.second;
if(pd.first!=-dis[u])
continue;
for(int &kd:G[kind[u]])
{
if(vis[kd]>50) //计数限制
continue;
vis[kd]++;
for(int &v:POS[kd])
{
if(dis[v]>dis[u]+abs(u-v))
{
dis[v]=dis[u]+abs(u-v);
q.push(P(-dis[v],v));
}
}
}
}
if(dis[n]==LINF)
dis[n]=-1;
cout<<dis[n]<<'\n';
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
D - Uddered but not Herd II
😦
E - Dance Mooves II
😦
F - Spaced Out
题意
要求为一张\(N\times N\)的图放置奶牛,要求每个\(2\times 2\)的子图中严格只存在\(2\)头牛
如果在位置\((i,j)\)放置一头牛,那么整张图的美观度将增加\(a_{i,j}\)
问能够获得的最大美观度
思路
先考虑第一行的选取情况,这里以一个\(01\)字符串来描述是否选择
例如,对于一个\(N=5\)的情况,\(01011\)就表示在\((1,2),(1,4),(1,5)\)三个位置放置奶牛
那么考虑这个字符串:
- 假如字符串中存在连续的两个\(0\)或者两个\(1\)
- 由于题目要求每个\(2\times 2\)的子图中都必须且只能放两头牛,所以确定了第一行的放置情况后,第二行的放置情况也就直接确定了(即第一行的放置情况按位取反),第三行的放置情况即第二行放置情况按位取反(等同于第一行),以此类推
- 对于这种情况,明显奇数行和偶数行分开考虑即可,所有奇数行全部加在一起形成一行,偶数行也全部加在一起形成一行
- 遍历这两行的每一列,取大作为答案即可
- 否则,就只有\(01\)交替或者\(10\)交替这两种情况
- 在这种情况下,我们可以将整张图按对角线翻转,行变成列、列变成行
- 那么这个\(01\)交替就可以直接看成是上一种情况行与行之间的变换
- 所以在这种情况下选择压缩列,将所有奇数列相加形成一列,偶数列也相加形成一列
- 最后遍历这两列的每一行,取大作为答案即可
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
int n,v[1050][1050];
int sum[2][1050];
int sum2[1050][2];
void solve()
{
cin>>n;
rep(i,1,n)
rep(j,1,n)
{
cin>>v[i][j];
sum[i%2][j]+=v[i][j];
sum2[i][j%2]+=v[i][j];
}
int ans=0,ans2=0;
rep(j,1,n)
ans+=max(sum[0][j],sum[1][j]);
rep(i,1,n)
ans2+=max(sum2[i][0],sum2[i][1]);
cout<<max(ans,ans2)<<'\n';
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
G - Dance Mooves I
题意
\(n\)头牛排成一排,初始时编号为\(i\)的牛在第\(i\)个位置
有\(k\)次交换,每次会交换\(a,b\)两个位置的牛
这\(k\)次交换将按顺序,无限循环执行下去
最后问每头牛能够到达的位置的个数
思路
可以先按照题意模拟一遍,求出\(k\)次交换后每头牛所在位置\(p[i]\),并记录在这\(k\)次交换中每头牛到过的地点的集合
题目要求无限次循环交换,但可以发现,加入编号为\(i\)的牛经过一遍交换后到达位置\(j\),编号为\(j\)的牛经过一遍交换后到达位置\(k\),那么编号为\(i\)的牛在经过两遍交换后也能够到达位置\(k\)(路径继承性)
所以可以借助并查集,将所有的\(x\)与\(p[x]\)合并,那么对于每个独立的集合,集合中的奶牛所能到达的地点是可以共享的
(我的做法,也可以用其余方法)最后枚举每个集合,再借助\(O(n)\)的set遍历,通过bitset来快速标记每个位置是否可到达,最后用count(bitset中1的个数)来表示这个集合中每头牛所能到达的位置数量
(竟然和标程用的时间一样欸……)
(给我2GB的空间,bitset直接代替set莽!时间应该会很优秀吧……)
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
int a[100050];
int gp[100050];
set<int> st[100050];
int fnd(int p)
{
return p==gp[p]?p:(gp[p]=fnd(gp[p]));
}
void merge(int a,int b)
{
if(a==b)
return;
int ga=fnd(a),gb=fnd(b);
gp[max(ga,gb)]=min(ga,gb); //往编号小的集合合并,方便遍历
}
vector<int> G[100050];
bool vis[100050];
int ans[100050];
void solve()
{
int n,k;
cin>>n>>k;
rep(i,1,n)
{
a[i]=i;
gp[i]=i;
st[i].insert(i);
}
rep(i,1,k)
{
int x,y;
cin>>x>>y;
st[a[x]].insert(y); //编号为a[x]的牛会到达位置y
st[a[y]].insert(x);
swap(a[x],a[y]);
}
rep(i,1,n)
{
merge(i,a[i]);
}
rep(i,1,n)
{
G[fnd(i)].pb(i); //记录每个集合内的牛的编号
}
bitset<100010> bs;
rep(i,1,n)
{
if(vis[i])
continue;
bs.reset();
for(int g:G[i])
{
vis[g]=true;
for(int id:st[g])
bs.set(id);
}
ans[i]=bs.count();
}
rep(i,1,n)
{
cout<<ans[fnd(i)]<<'\n';
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
H - No Time to Paint
题意
有一面墙,初始时是空的,Bessie想涂墙,目标墙的状态可以描述成一个大写字母组成的字符串,字母表示颜色
每次涂墙Bessie都可以选择一段连续的墙面涂色
Bessie涂墙有一个原则,它不会用亮色来覆盖暗色(字典序大的字母较暗),只会用暗色覆盖亮色;初始时如果墙上没有颜色则不会受到约束
有\(Q\)次询问,每次询问会给定一段区间\([l,r]\),表示Bessie此时不会去涂这段区间内的墙(即保持无颜色),而对于其他部分则还是需要涂色
对于每次询问,输出最少的涂色次数
思路
主要根据“不会用亮色来覆盖暗色”这个条件来解题(刚开始没看到感觉好难结果是水题)
主要思路是从左往右预处理涂某个位置左边的墙的花费\(ansl\),从右往左预处理涂某个位置右边的墙的花费\(ansr\)
最后对于询问的\([l,r]\),明显\(ansl[l-1]+ansr[r+1]\)即答案
预处理(从左往右为例):
由于亮色不会覆盖暗色,换言之只能用字典序大的字母覆盖字典序小的字母
那么对于每个位置,先检查左侧是否有相同的字母,如果没有的话涂色数肯定是要\(+1\)的
否则,检查当前位置与前一个出现这个字母的位置之间是否有字典序更小的字母,如果存在的话则表示当前这个字母不能同前一个字母一起涂成长条,此时涂色数要\(+1\)
否则,当前字母可以和前一个字母一起涂色,方案数继承即可
右往左的处理相同
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
int n;
char s[100050];
int ansl[100050],ansr[100050];
int lstp[128];
void deal()
{
ansl[0]=ansr[n+1]=0;
mst(lstp,0); //记录每个字母最后一次出现的位置
rep(i,1,n)
{
if(lstp[s[i]]==0) //左侧没有出现过当前这个字母
{
ansl[i]=ansl[i-1]+1;
}
else
{
int f=0;
repp(j,'A',s[i]) //枚举所有字典序比s[i]小的字母
if(lstp[j]>lstp[s[i]]) //如果字典序小的字母在两者中间(直接判断最后一次出现的位置即可)
{
f=1;
break;
}
ansl[i]=ansl[i-1]+f;
}
lstp[s[i]]=i;
}
mst(lstp,INF);
per(i,n,1)
{
if(lstp[s[i]]==INF)
{
ansr[i]=ansr[i+1]+1;
}
else
{
int f=0;
repp(j,'A',s[i])
if(lstp[j]<lstp[s[i]])
{
f=1;
break;
}
ansr[i]=ansr[i+1]+f;
}
lstp[s[i]]=i;
}
}
void solve()
{
int q;
cin>>n>>q;
cin>>s+1;
deal();
while(q--)
{
int l,r;
cin>>l>>r;
cout<<ansl[l-1]+ansr[r+1]<<'\n';
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
I - Just Stalling
题意
给定\(N\)只牛和\(N\)个房子,每只牛只能住进高度比它高的房子,要求为每头牛分配一个房子,问有多少种分配方案
思路
为所有牛和所有房子进行排序
从高往低遍历每头牛,再设置一个指针来处理当前牛能够住的最矮的房子(的前一个位置)
由于房子也是排序后的,所以具有单调性,每次只需要让指针持续向前移动即可
发现前一头牛(较高的)选了一间可以住的房子后,后一头牛(较低的)的选择方案(不考虑已被占用的话)是只增不减的
所以每头牛的方案数就是总共的选择方案(不考虑已被占用的)减去已被占用的数量即可,总方案数为累乘
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef unsigned long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
ll a[30],b[30];
void solve()
{
int n;
cin>>n;
rep(i,1,n)
cin>>a[i];
rep(i,1,n)
cin>>b[i];
sort(a+1,a+n+1);
sort(b+1,b+n+1);
ll ans=1;
int p=n;
per(i,n,1)
{
while(p>0&&b[p]>=a[i])
p--;
ans*=n-p-(n-i);
}
cout<<ans<<'\n';
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
J - Min_Prime
范围只有\(10^7\),直接通过埃氏筛法处理出每个数的最小质因子,再全部做一次xor即可
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
int minfac[10000010];
void solve()
{
int n;
cin>>n;
int ans=0;
rep(i,2,n)
{
if(!minfac[i])
{
minfac[i]=i;
for(ll j=i+i;j<=n;j+=i)
if(!minfac[j])
minfac[j]=i;
}
ans^=minfac[i];
}
cout<<ans<<'\n';
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
K - Even More Odd Photos
题意
\(N\)头牛,每头牛都有一个值
FJ想将这\(N\)头牛分组,第一组中所有牛的值相加为偶数,第二组为奇数,第三组为偶数,如此交替类推
要求每头牛都必须分组
问最多能够分多少组
思路
典型分奇偶性讨论的问题
直接处理出奇数值与偶数值的个数
- 首先,最优方案肯定是偶数放一头单独成组,奇数再放一头单独成组,以此类推
- 直到偶数或者奇数两者有一个数量为\(0\)了,开始讨论:
- 如果剩下的是偶数,那么只能全部单独成组,组数\(+1\)即答案
- 如果剩下的是奇数,那么可以通过“奇+奇=偶”,每\(3\)头牛可以分成\(2\)组
- 最后判断剩余的牛的数量,由于上面的方案最后一组是奇数,所以如果最后奇数牛剩\(2\)头,刚好组数\(+1\),但如果只剩一头,不能分配,则只能和最后一组的奇数牛合成偶数加入偶数组,此时组数应当\(-1\)(\(N\ge 2\),不需要考虑这一步的非法情况)
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
int cnt[2];
void solve()
{
int n;
cin>>n;
rep(i,1,n)
{
int d;
cin>>d;
cnt[d%2]++;
}
int ans=0;
while(cnt[0]&&cnt[1])
{
ans+=2;
cnt[0]--;
cnt[1]--;
}
if(cnt[0]) //剩下的全是偶数
{
ans++;
}
else if(cnt[1])
{
ans+=cnt[1]/3*2; //3头分2组
cnt[1]%=3;
if(cnt[1]==1)
{
ans--;
}
else if(cnt[1]==2)
{
ans++;
}
}
cout<<ans<<'\n';
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
solve();
return 0;
}
L - Balanced Subsets
防AK 😦