北京师范大学第十六届程序设计竞赛决赛-重现赛 ACDEFGI

校队师兄拉了这套题给我们热身练习,故此写一下题解记录(2020-7-14)

A.塞特斯玛斯塔

真正的签到题,题目虽然很长但是真正有用的只有最后一句话,判断是否有“MILLION Master”,有则输出“MILLION Master”,无则输出“NA Noob”。

#pragma GCC optimize(2)
#include<bits/stdc++.h> 
using namespace std;
#define endl "\n"
string str;
int main(){
    ios_base::sync_with_stdio(0);cin.tie(0);
   int t,n,f;cin>>t;
   while(t--)
   {
   		f=0;cin>>n;
        for(int i=0;i<n;i++){
            cin>>str;
            if(str!="PERFECT")f=1;
        }
        if(f)cout<<"NA Noob"<<endl;
        else cout<<"MILLION Master"<<endl;		
   }
   return 0;
} 

C.萌萌哒身高差

题意:求1-n的全排列中每个排列相邻差绝对值之和的期望值。
题解:赛后查了下发现有很多方法,特别是(n*n-1)/3的公式很神奇,像我这种数学这么差的菜鸟肯定是想不到的
在此我说说我的方法:首先我们在中学就学过E(X+Y)=E(X)+E(Y),以此为启发,求排列相邻差绝对值的期望=每个点差绝对值的期望值之和,故此我们可以求出每个数可能出现的各个种差值之和再除以点数即可(需要清楚的是(1,2)和(2,1)虽然差值的绝对值相等,但是一左一右,在排列中出现的情况是不同的)

#pragma GCC optimize(2)
#include<bits/stdc++.h> 
using namespace std;
#define ll long long
#define endl "\n"
int main(){
    ios_base::sync_with_stdio(0);cin.tie(0);
   int t;cin>>t;
   while(t--)
   {
   	  int n;cin>>n;
   	  ll sum=0;
   	  for(int i=1;i<=n;i++)
   	  for(int j=1;j<=n;j++)
   	  sum+=abs(i-j);
   	  cout<<fixed<<setprecision(12)<<1.00*sum/n<<endl;
   }
   return 0;
} 

D.雷电爆裂之力

题意:一共四条路南北走向(上下),前三条路分别有n,m,k个路口,每条路的每个路口都有一个坐标,只有在路口才可以从东到西(从左到右),走到下一条路的,在没有到达路口的情况下只能向北或者向南走。从西到东依次是京师路,木铎路,金声路和新街口外大街的最短路。
题解:以第二条路b为基准,分别第一条路a和第三天路c的两点位置差绝对值进行二分,贪心选出最小的情况。
需要注意的是记得开longlong,无穷大INF也记得选≥1e12的。

#pragma GCC optimize(2)
#include<bits/stdc++.h> 
using namespace std;
#define ll long long
#define endl "\n"
const ll INF=0x3f3f3f3f3f3f3f3f;
ll a[100050],b[100050],c[100050];
int main(){
    ios_base::sync_with_stdio(0);cin.tie(0);
   int t;cin>>t;
   while(t--)
   {
       int n,m,k;cin>>n>>m>>k;
       for(int i=1;i<=n;i++)cin>>a[i];
       for(int i=1;i<=m;i++)cin>>b[i];
       for(int i=1;i<=k;i++)cin>>c[i];
       ll ans=INF;
       for(int i=1;i<=m;i++)
       {
       	    ll d1=INF,d2=INF;
       		ll p1=lower_bound(a+1,a+1+n,b[i])-a;
			d1=min(d1,abs(b[i]-a[p1-1]));
       		if(p1<=n)
       		d1=min(d1,abs(a[p1]-b[i]));
       		ll p2=lower_bound(c+1,c+1+k,b[i])-c;
       		d2=min(d2,abs(b[i]-c[p2-1]));
       		if(p2<=k)
       		d2=min(d2,abs(c[p2]-b[i]));
       		ans=min(ans,d1+d2+3);
	   }
       cout<<ans<<endl;
   }
   return 0;
} 

E.可以来拯救吗

题意:在长度为n的数组中,所有长为k的子序列的和的平方的异或和。
题解:没有什么技巧直接暴力dfs深搜各种情况,如果非要有技巧就是C(n,k)=c(n,n-k),当k>n-k时,预处理总和,再用总和减去这n-k个数的和,就是剩下k个数的和。

#pragma GCC optimize(2)
#include<bits/stdc++.h> 
using namespace std;
#define ll long long
#define endl "\n"
ll a[100050],n,k,sum,f,ans;
void dfs(int id,int num,ll now)
{
    if(num==k){
        if(f)ans^=(sum-now)*(sum-now);
        else ans^=(now*now);
        return ;
    }
    for(int i=id;i<=n;i++)
    dfs(i+1,num+1,now+a[i]);
}
int main(){
    ios_base::sync_with_stdio(0);cin.tie(0);
   int t;cin>>t;
   while(t--)
   {
       cin>>n>>k;
       sum=f=ans=0;
       if(k>n-k)f=1,k=n-k;
       for(int i=1;i<=n;i++)cin>>a[i],sum+=a[i];
       dfs(1,0,0);
        cout<<ans<<endl;
   }
   return 0;
} 

F.汤圆防漏理论

题意:无向完全图,每个点的权值等于与它连接各边的权值之和,每次删除一个点需要消耗代价是该点的权值,与它链接的个点的权值都减去相应连接的边的权值,求删除完全部点每次消耗的权值最大值最小。
题解:预处理各个点权值和,用vector<pair<ll,ll>>b[]存储个点的边和权值,通过set<pair<ll,ll> >st动态选出每次未选出且权值最小的点,每次选出一个点,通过预先用vector<pair<ll,ll>>b[]存储的边和权值,取出与该删去点相邻的点删除,减去与之相邻边的权值再重新添入set中。记得每次删去某个点后记得标记,标记过的点就不对其操作。

#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl "\n"
#define p pair<ll,ll>
const int MAX=1e5+5;
vector<pair<ll,ll> >b[MAX];
set<pair<ll,ll> >st;
ll a[MAX],vis[MAX];
int main(){
    ios_base::sync_with_stdio(0);cin.tie(0);
   int t;cin>>t;
   while(t--)
   {
        ll n,m;cin>>n>>m;
        for(int i=1;i<=m;i++)
        {
            ll u,v,w;cin>>u>>v>>w;
            a[u]+=w;a[v]+=w;   
            b[u].push_back(make_pair(v,w));
            b[v].push_back(make_pair(u,w));   
        }
        for(ll i=1;i<=n;i++)
        st.insert(make_pair(a[i],i));
        ll ans=0;
        while(!st.empty())
        {
            auto tp=st.begin();
            ans=max(ans,tp->first);
            vis[tp->second]=1;
            st.erase(st.begin());
            for(int j=0;j<b[tp->second].size();j++)
            {
                ll v=b[tp->second][j].first,w=b[tp->second][j].second;
                if(vis[v]==0){
                    st.erase(make_pair(a[v],v));
                    a[v]-=w;
                    st.insert(make_pair(a[v],v));
                }
            }
        }
        cout<<ans<<endl;
        for(int i=0;i<=n;i++)
            b[i].clear();
        st.clear();
        for(int i=0;i<=n;i++)
            a[i]=0,vis[i]=0;
   }
   return 0;
}

G.命名规范问题

题意:按照3个规则修改字符串。
1.每个变量名由至少2个单词拼接构成,且每个单词长度至少为2;
2.每个单词的首字母必须大写,其他位置必须小写(除了变量名的第一个单词允许全部小写外)
3.下划线命名法,规则比较简单,即各个单词之间用下划线’'连接,且字母全部小写。
如果变量名符合驼峰命名法规范则将其改为下划线命名法,否则不变。
题解:直接暴力模拟即可。
需要注意:
1.满足下划线命名法,需要满住全部单词都小写,且首单词前不需要下划线“

2.每个单词长度≥2,故两个大小字符连续出现、某个大写字符出现在字符串最后、整个字符串只有一个大写字符且出现在前二的位置,都是不满足下划线规则的

#pragma GCC optimize(2)
#include<bits/stdc++.h> 
using namespace std;
#define endl "\n"
int a[25];
int main(){
    ios_base::sync_with_stdio(0);cin.tie(0);
   int t;cin>>t;
   while(t--)
   {
   		string s;cin>>s;
   		int cnt=1;
   		for(int i=1;i<s.size();i++)
		{
			if(s[i]>='A'&&s[i]<='Z'){
				a[++cnt]=i;
			}
		}
		int f=0;
		if(cnt==1||a[cnt]==s.size()-1){
			f=1;
		}
		else {
			for(int i=1;i<cnt;i++)
				if(a[i+1]-a[i]<2){
					f=1;break;
				}
		}
   		if(f){
   			cout<<s;	
		}
		else {
			for(int i=0;i<s.size();i++)
			{
				if(s[i]>='A'&&s[i]<='Z'){
					if(i!=0)cout<<"_";
					cout<<char(s[i]+'a'-'A');
				}
				else cout<<s[i];
			}
		}
		cout<<endl;
   }
   return 0;
} 

I.如何办好比赛

题意:n个人排序,每个萌新的期待度=前面有多少个大佬,求需要与相邻交换多少次使得期望值恰好为k
题解:从1到n,通过统计大佬D出现的次数,每次遇到一个萌新,记录当时的期待值M,需要交换的次数abs(k-M),因为期待值可能大或者小故需要一个绝对值。因为肯定存在萌新的旁边的大佬,萌新与大佬交换一次,期待值变化值一定是1。
期望值最大的情况是所有的萌新都在大佬后面,即D*(n-D),当D*(n-D)<k时无解。

#pragma GCC optimize(2)
#include<bits/stdc++.h> 
using namespace std;
#define ll long long
#define endl "\n"
int main(){
    ios_base::sync_with_stdio(0);cin.tie(0);
   int t;cin>>t;
   while(t--)
   {
   	 ll n,k;cin>>n>>k;
   	 string s;cin>>s;
   	 ll d=0,m=0;
   	 for(ll i=0;i<n;i++)
   	 {
   	 	if(s[i]=='D')d++;
		else if(s[i]=='M')m+=d;	
	 }
	 if((n-d)*d<k)
   	 cout<<-1<<endl;
   	 else cout<<abs(m-k)<<endl;
   }
   return 0;
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值