武科训练赛题解

武科训练赛题解

1.A-ximena的倍数_2022WUSTACM训练赛 (nowcoder.com)

求一个数的k的倍数数位和的最小值。这个题目的关键是数位和,数的本身并不关心。所以只需要求每个数位上的和就好。很明显每一个数他的每一位都是可以通过乘10和加+1操作形成的。但是加一是很方便的,乘上一个10是很不容易操作的。所以我们考虑用01bfs。01bfs有什么好处呢,当乘上10的时候我只需要再末尾加上一个0就好了(也就是乘以10)。由于10肯定优于操作+1,所以优先考虑10。这个时候考虑用deque,更优解放在头部。还有就是这么k的倍数可以是无穷,如何处理这个问题呢?我们只需要考虑取模k后的值,因为当这个值等于0时就是解。

#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define fel(i,x,y) for(int i=x;i<=y;i++)
#define fhl(i,x,y) for(int i=x;i>=y;i--)
#define inf 0x3fffffff
#define ll long long
#define pb push_back
#define endl "\n"
typedef pair<int,int> PII;
const int N=1e5+100;
int k;
bool vis[N];
void bfs(){
deque<PII>q;
q.push_front({1,1});//第一个表示数的大小,第二个表示数的数位和
vis[1]=1;
while(!q.empty()){
auto [x,y]=q.front();
q.pop_front();
if(x==0){
cout<<y<<endl;
return;
}
if(!vis[x*10%k]){
q.push_front({x*10%k,y});
vis[x*10%k]=1;
}
if(!vis[x+1]) q.push_back({x+1,y+1});

}
}
int main()
{
   cin>>k;
   bfs();
   return 0;
}

2.B-ximena与图上难题_2022WUSTACM训练赛 (nowcoder.com)

考虑让每个棋子才踩在自己的点上才算结束,我考虑以这种情况作为结束1234567890,然后bfs搜索,每次将坐标上没有棋子的那个点与他有边相连的点交换。然后继续bfs

#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define fel(i,x,y) for(int i=x;i<=y;i++)
#define fhl(i,x,y) for(int i=x;i>=y;i--)
#define inf 0x3fffffff
#define ll long long
#define pb push_back
#define endl "\n"
//就是每次bfs看有没有点可以踩到自己对点上,如果有那就不再动他。
//主要是还存在一个问题,我考虑先让那个点动呢?
//我觉得应该先知道每个点距离他的终点的最近距离,然后先搜这个点。这个感觉可以用dfs搜一下int m,x,y,ans;
//还是没想到
//最后解法就是将每个点带到自己点上,也就是这种情况123456780
//然后每次对那个空的点进行搜索,看哪些点可以到这个位置上。
#define int long long
typedef pair<int,int>PII;
const int N=20;
int m,x,y,ans,t;
int a[N];
vector<int>b[N];
map<int,int>vis;
void bfs(){
queue<PII>q;
t=0;
for(int i=1;i<=9;i++){
t=t*10+a[i];
}
//cout<<t<<endl;
vis[t]=1;
q.push({t,0});
while(!q.empty()){
auto [now,step]=q.front();
if(now==123456780){
cout<<step;
return;
}
//cout<<now<<endl;
q.pop();
for(int i=9;i>=1;i--){
a[i]=now%10;
now/=10;
}
for(int i=1;i<=9;i++){
if(a[i]>0) continue;
for(auto j:b[i]){
swap(a[j],a[i]);
t=0;
for(int k=1;k<=9;k++){
t=t*10+a[k];
}
if(!vis[t]){
q.push({t,step+1});
vis[t]=1;
}
swap(a[j],a[i]);
}
}
}
  cout<<-1<<endl;
}
signed main(){
cin>>m;
fel(i,1,m){
cin>>x>>y;
b[x].pb(y);
b[y].pb(x);
}
for(int i=1;i<=8;i++){
cin>>t;
a[t]=i;
}
bfs();
return 0;
}

3.C-ximena的序列_2022WUSTACM训练赛 (nowcoder.com)

拓扑排序。然后需要考虑尽量字典序大,考虑用一个优先队列,建成小顶堆就可以了。这样就可以保证都可以输出的情况下先输出小的。

#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define fel(i,x,y) for(int i=x;i<=y;i++)
#define fhl(i,x,y) for(int i=x;i>=y;i--)
#define inf 0x3fffffff
#define ll long long
#define pb push_back
#define endl "\n"
const int N=2e5+100;
int n,m;
vector<int>b[N];
int in[N];//入度
vector<int>ans;
void topsort(){
priority_queue<int,vector<int>,greater<int> >q;//用这个的原因是要保证1在比他大的前面要先输出
for(int i=1;i<=n;i++){
if(!in[i])
q.push(i);
}
while(!q.empty()){
auto now=q.top();
       ans.pb(now);
       if(ans.size()>n) break;
q.pop();
for(auto i:b[now]){
in[i]--;//
if(!in[i]){
q.push(i);
}
}
}
}
int main(){
cin>>n>>m;
fel(i,1,m){
int x,y;
cin>>x>>y;
b[x].pb(y);//有向图
in[y]++;
}
topsort();
   if(ans.size()==n){
       for(auto i:ans){
           cout<<i<<" ";
      }
  }
   else cout<<-1<<endl;
return 0;
}

4.E-Ximena打羽毛球_2022WUSTACM训练赛 (nowcoder.com)

也是拓扑排序。考虑两个球的入度都位0才可以把两个球都删掉。所以就直接正常拓扑排序,然后用一个集合保存所有的球,当这个球两个都出去之后,把这个数从集合中删去。最后集合为空即可取出全部球。

#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define fel(i,x,y) for(int i=x;i<=y;i++)
#define fhl(i,x,y) for(int i=x;i>=y;i--)
#define inf 0x3fffffff
#define ll long long
#define pb push_back
#define endl "\n"
//感觉好像确实是拓扑排序的裸题,就是每次看入度为0的两个点是否是一样的,如果是一样的。
//然后因为有两个球,可以考虑一个球是i,一个n+i;用一个vis标记一下就好。
//但是再想用优先队列他可以保存数,然后让数多的排前面啊,但是好像不行,裂开
//那就考虑球不分了,因为你发现肯定要两个球的入度都为0才可以把两个球都删除
//那就很容易考虑了,用一个集合存下所有的球,每次入度为0的点就把他删除
//最后集合为空就可以全部删除
const int N=1e6+10;
int n,m;
set<int>s;
vector<int>b[N];
int in[N];
void topsort(){
queue<int>q;
for(int i=1;i<=n;i++){
if(!in[i]) q.push(i);
s.insert(i);
}
while(!q.empty()){
auto now=q.front();
q.pop();
s.erase(now);
for(auto i:b[now]){
in[i]--;
if(!in[i])q.push(i);
}
}
if(s.empty()) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
int main(){
cin>>n>>m;
for(int i=1,x,u,v;i<=m;i++){
cin>>x,cin>>u;
for(int j=1;j<x;j++){
cin>>v;
b[u].pb(v);
in[v]++;
u=v;
}
}
topsort();
return 0;
}

5.F-ximena逛超市_2022WUSTACM训练赛 (nowcoder.com)

代码上有很详细的解析。

#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define fel(i,x,y) for(int i=x;i<=y;i++)
#define fhl(i,x,y) for(int i=x;i>=y;i--)
#define inf 0x3fffffff
#define ll long long
#define pb push_back
#define endl "\n"
//状态表示很简单就是 f[i]表示选择前i个物品的最大价值,
//状态转移就是找到i前面比i小的点的的f[j]+b[i]的最大值就好
//但是很明显,每次把前面的全都遍历时间复杂度很高,
//树状数组和线段树求一下区间最值
//发现a的取值范围是比较小的,所以可以考虑以这个建树
//然后只需要找比a[i]小的区间的最大值就好了
//然后遍历的时候更新一下树就ok
#define int long long
const int N=2e5+10;
int n;
int a[N],b[N],dp[N];
//不需要建树了,一边遍历一遍建树
int tree[N<<2];
void push_up(int p){
tree[p]=max(tree[p*2],tree[p*2+1]);
}
inline void update(int pos,int l,int r,int p,int k){//只要更新1-pos就好了,比pos大的区间不用更新
if(l==r){
tree[p]=k;
return;
}
int mid=l+r>>1;
if(pos<=mid)update(pos,l,mid,p*2,k);
else update(pos,mid+1,r,p*2+1,k);
push_up(p);//更行l单点再回溯上去就好了
}
inline int query(int nl,int nr,int l,int r,int p){
if(nl<=l&&nr>=r){
return tree[p];
}
int mid=l+r>>1;
int res=0;
if(nl<=mid) res=max(res,query(nl,nr,l,mid,p*2));
if(nr>mid) res=max(res,query(nl,nr,mid+1,r,p*2+1));
return res;
}
signed main(){
cin>>n;
fel(i,1,n) cin>>a[i];
fel(i,1,n) cin>>b[i],dp[i]=b[i];
int ans=0;
fel(i,1,n){
int res=0;
res=query(1,a[i],1,n,1);//1-a[i]区间内的最大值
dp[i]=res+b[i];
ans=max(dp[i],ans);
update(a[i],1,n,1,dp[i]);//单点更新,没咋写过单点更新的线段树,应该差不多,1-a[i]区间内的最大值更新,也就是把a[i]对应的点给更新;
}
   cout<<ans;
return 0;
}
 

 

posted @ 2022-07-27 18:37  silky__player  阅读(26)  评论(0)    收藏  举报