当前位置: 代码迷 >> 综合 >> Nordic Collegiate Programming Contest 2019赛后总结
  详细解决方案

Nordic Collegiate Programming Contest 2019赛后总结

热度:60   发布时间:2023-12-14 05:08:43.0

部分题解

A题

  • 单词接龙,要求上一个人所说单词的最后一个字符要和你说的第一个字符相同。给你上一个人说的单词,再给你一组单词,让你从这中间选择单词接龙,找不到输出“ ?”,能找到的话让你的下一个人继续从这些单词里选择单词接龙,当然不能选择你说过的单词,如果他找不出来就说明你获胜了,输出你可能说的单词在这一组单词中第一个出现的(符合要求的单词可能不止一个)后面加一个“!”;他能找出来的话就不用输出这个“!”。
  • 数据范围虽然是n5,但是数据似乎不是太强,O(n2)的也可以过。
#include <bits/stdc++.h>
using namespace std;
const int MAXN=1e5+100;
string Data[MAXN];
int main(){
    string s;bool flag=0;cin>>s;int len=s.size()-1;int n;cin>>n;for(int i=0;i<n;i++) {
    cin>>Data[i];//151ms,用scanf能降到21msif(Data[i][0]==s[len]) flag=1;}int tmp=-1;if(!flag) cout<<'?';else for(int i=0;i<n;i++){
    //flag==1;flag=1;if(s[len]==Data[i][0]){
    if(tmp==-1) tmp=i;for(int j=0;j<n;j++){
    if(Data[i]!=Data[j]&&Data[i][Data[i].size()-1]==Data[j][0]) {
    flag=0;//如果对手能接上,说明这个解结束了,应该要breakbreak;//这个地方当时忘了break,卡最后一组数据 头大}}if(flag==1) {
    cout<<Data[i]<<'!';break;}}if(i==n-1) cout<<Data[tmp];}return 0;
}

B题

  • 给你三个方块,长宽都已知,求这三个方块占据的最小的矩形面积。
  • 这个问题我始终觉得有好方法,但是没有想出来,比较直观的想法就是暴力计算所有可能的值,取最小值一定是答案,挺麻烦,可以这样想:定一个矩形,另外两个矩形做变化,那么有这样四种情况,三个矩形在同一条直线同侧、有两个矩形在同一条直线同侧(根据排列组合知识易得有三种情况)这样进行计算。
  • 109要开long long
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll block[5][5];
int main(){
    int n,t;cin>>t;while(t--){
    ll max_l=0,max_w=0;ll ans;for(int i=0;i<3;i++){
    cin>>block[i][0]>>block[i][1];if(block[i][0]<block[i][1]) swap(block[i][0],block[i][1]);//长和宽max_l+=block[i][0];max_w+=block[i][1];}ans=max_l*max_w;//最大情况for(int i=0;i<2;i++){
    //在一直线同侧的情况,通过定两个矩形,旋转另一个分别计算for(int j=0;j<2;j++){
    for(int k=0;k<2;k++){
    int a=(i+1)%2,b=(j+1)%2,c=(k+1)%2;//另外一条边max_l=block[0][i]+block[1][j]+block[2][k];//长max_w=max(max(block[0][a],block[1][b]),block[2][c]);//宽ans=min(max_l*max_w,ans);}}}for(int i=0;i<2;i++){
    for(int j=0;j<2;j++){
    for(int k=0;k<2;k++){
    int a=(i+1)%2,b=(j+1)%2,c=(k+1)%2;max_l=max(block[0][i]+block[1][j],block[2][k]);//长max_w=max(block[0][a],block[1][b])+block[2][c];//宽ans=min(max_l*max_w,ans);max_l=max(block[0][i],block[1][j]+block[2][k]);//长max_w=block[0][a]+max(block[1][b],block[2][c]);//宽ans=min(max_l*max_w,ans);max_l=max(block[0][i]+block[2][k],block[1][j]);//长max_w=max(block[0][a],block[2][c])+block[1][b];//宽ans=min(max_l*max_w,ans);}}}cout<<ans<<endl;}return 0;
}

C题

  • 一块已知长度和宽度的巧克力,每次可以沿着水平或者垂直方向打碎,当然生成的每部分长和宽都是整数,操作之后把巧克力分成两堆,问需要操作多少次生成的巧克力中能够有一堆出现目标面积。
  • 一道数学题,建立模型是关键,这个问题可以抽象为如果一个数a=n*k+p,从一个m×n矩阵中需要分多少次,以下图为例
    在这里插入图片描述
    图中蓝色部分是n*k+p,很明显要切3次可以切出来,如果没有p部分只需要一次,如果没有p部分k再向上挪一点就是两次,一共就这几种情况。所以输出答案应该在1-3之间。
  • 那么如果是一次切很明显应该是所需要矩形面积整除m或者n,两次切应该是切完之后可以表示成为一个(0,m)的数和一个(0,n)的数的乘积,千万注意:不仅要看p,还要看剩余部分即n*m-p,考虑质数,比如输入5 5 23,23只能拆成1×23,所以你如果不考虑2的话因为23>5,答案肯定是错的。切三次就是剩下的情况了。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
    ll n,m,a;cin>>n>>m>>a;ll rest=n*m-a;int ans=0;for(int i=1;i<=n;i++){
    if(a%i==0&&a<=i*m){
    //等于的时候意思是有一边重合,那么一次就可以切完if(i*m!=a) {
    if(!ans) ans=2;}//要最少次数,2要一个就行了else ans=1;}}for(int i=1;i<=m;i++){
    if(a%i==0&&a<=i*n){
    if(i*n!=a) {
    if(!ans) ans=2;}else ans=1;}}for(int i=1;i<=n;i++){
    if(rest%i==0&&rest<=i*m){
    //操作和上面一样if(i*m!=rest) {
    if(!ans) ans=2;}else ans=1;}}for(int i=1;i<=m;i++){
    if(rest%i==0&&rest<=i*n){
    if(i*n!=rest) {
    if(!ans) ans=2;}else ans=1;}}if(ans==0) ans=3;cout<<ans;return 0;
}

F题

  • 把一些小人分成若干个小组抵挡敌人的进攻,采用回合制,每个小人可以对敌人造成1点伤害,敌人可以使用技能,一个技能可以杀死你一个小组里的固定人数,如果剩下的人数不够就那个小组剩下的都被杀死,问你怎么安排队伍能够对敌人造成更多伤害,输出伤害总量。
  • 贪心,问题是怎么贪心,组里面人数为1越多越好?实测这个做法不对,看了题解,是把小人按照敌人每一次技能伤害的值分成若干个小队,比如一次技能的伤害是k,小人总数是n,那么要把所有小人划分成n=g*k+x,g是组数,x是剩下的人数。再把剩下这x个小人平分到m个组中,m是输入的最大分组数,那么在这里面其实就一个自由变量g,而g的范围呢?由于x的范围是[0,m×k],推导得到g最大是? n/k ?,最小是? n/k ?-m,小于0没有意义,所以应加上大于或者等于0的限制。然后就很神奇的通过了。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll ans,n,m,k;
ll cal(ll group){
    ll ret=(2*n-group*k)*(group+1)/2;ll x=n-group*k;ll tim=x/m;ll yu=x%m;ll rest=m-yu;ret+=(tim+tim*rest)*rest/2;ret+=(rest*tim+tim+1+rest*tim+(yu-1)*(tim+1))*(yu-1)/2;return ret;
}
int main(){
    cin>>n>>m>>k;m=min(n,m);//分组的最大数量,实测不写也能过for(ll i=n/k;i>=n/k-m&&i>=0;i--){
    //ans=max(ans,cal(i));}cout<<ans<<endl;return 0;
}

G题

  • 给你一组数据,代表每天的温度,让你寻找连续三天之间第一天和第三天温差最小的,输出这种情况下第一天位于数据的第几个和这两天温度最高的一天,如果有多种情况输出第一组。
  • 数据范围很小。
#include <bits/stdc++.h>
using namespace std;
const int MAXN=1e5+100;
int Data[MAXN];
int main(){
    int n;cin>>n;for(int i=0;i<n;i++) cin>>Data[i];int ans=0x3f3f3f3f;int pos;for(int i=1;i<n-1;i++){
    int tmp=max(Data[i-1],Data[i+1]);if(tmp<ans) {
    ans=min(tmp,ans);pos=i;}}cout<<pos<<' '<<ans;return 0;
}

I题

  • 你是一个杂货店管理员,要往冰箱里面填充苏打水,要保证顾客尽可能的拿到冷的水,但是你比较懒,不想把所有的水都拿出来,所以你只是在前面填充,而顾客只会在前面拿水,问你要怎么做才能使得顾客拿到冷水的概率大。
  • 题意需要仔细理解,你填上苏打水就相当于这个冰箱没有了,因为顾客只会去前面拿水,你填上的水是常温的,顾客不要,他也不会去后面拿,所以应该从剩下苏打水最少的冰箱里面开始填充,填充完了之后看看你没填充过的冰箱剩下的冷水够不够顾客购买的就行了(不知道考的是什么)。
#include <bits/stdc++.h>
using namespace std;
int Data[300];
int arr[300];
int main(){
    int n,m,s,d;int sum=0;cin>>n>>m>>s>>d;for(int i=0;i<s;i++) {
    cin>>Data[i];sum+=Data[i];}bool flag=1;for(int i=0;i<=d&&flag;i++){
    //从最少的开始填充for(int j=0;j<s;j++){
    if(Data[j]==i){
    arr[j]+=min(n,d-Data[j]);sum-=Data[j];//冷苏打n-=arr[j];if(sum<=0||n<=0){
    flag=false;break;}}}}//cout<<sum<<' '<<m<<endl;if(sum<m) cout<<"impossible";else{
    for(int i=0;i<s;i++) cout<<arr[i]<<' ';}return 0;
}

欢迎留言指点

  相关解决方案