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

  今天打了北师的校内比赛?官方说是个人赛,我们组队打才过5个。。。北师果然强。

   今天他需要补的题很多,主要是:

    D雷电爆裂之力;F汤圆防漏理论;E可以来拯救吗;

    首先是D雷电爆裂之力(比赛的时候没什么思路,就交给队友了,赛后看题解发现真的不难。。。)

     链接:https://www.nowcoder.com/acm/contest/117/D
来源:牛客网


题目描述 
听说导体切割磁感线可以发电?
 
今天,zhuaiballl想要做切割磁感线运动,感受雷电的力量。
 
南北方向有四条马路,从西到东依次是京师路,木铎路,金声路和新街口外大街,可以看成是四条平行的数轴,且相邻的两条数轴之间距离为1m。东西走向有许多小道连接了相邻的马路。假设地磁南极和地理北极重合,地磁北极和地理南极重合。现在zhuaiballl位于京师路的某个位置,想要出发前往新街口外大街,速度为1m/s。由于可能没有一条路径从西到东一直连向新街口外大街,所以每次遇到丁字路口时,zhuaiballl需要选择是往左走还是往右走,样例如下图所示。
 


 
zhuaiballl想要感受尽可能强的雷电力量,所以希望从他开始往东走时开始,到他到达新街口外大街所需要的时间尽可能短。
 
现在,给你附近的地图,你能否求出从zhuaiballl开始往东走时开始,到他到达新街口外大街的最短时间?
输入描述:
第一行是一个正整数T(≤ 20),表示测试数据的组数,


对于每组测试数据,


第一行是一个整数n,m,k(1≤ n,m,k ≤ 100000),分别表示连接京师路与木铎路,木铎路与金声路,金声路与新街口外大街的道路个数,


第二行包含n个以空格分隔的整数a1,a2,...,an,表示连接京师路与木铎路的各个小道的南北方向坐标(单位:m),


第三行包含m个以空格分隔的整数b1,b2,...,bm,表示连接木铎路与金声路的各个小道的南北方向坐标(单位:m),


第四行包含k个以空格分隔的整数c1,c2,...,ck,表示连接金声路与新街口外大街的各个小道的南北方向坐标(单位:m),


保证每行坐标按严格递增的顺序给出,并且坐标绝对值不超过109。
输出描述:
对于每组测试数据,输出一行,包含一个整数,表示答案(单位:s)。
示例1
输入


1
3 3 2
-1 1 4
-3 2 4
-1 1
输出

5

题意:有三个长度分别为 n, m, k 的元素值严格递增的整数数组

a, b, c。求 min(abs(ai − bj) +abs(bj − cp)) + 3 的值,其中

1 ≤ i ≤ n, 1 ≤ j ≤ m, 1 ≤ p ≤ k

做法:一个比较简单的思路是枚举  bj,找到距离bj 最近的 ai cp,更新答案。

#include<stdio.h>
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod =1000000007;
int t;
int n,m,k;
int a[100005],b[100005],c[100005];
int main()
{
    cin>>t;
    while(t--){
        cin>>n>>m>>k;
        for(int i=1;i<=n;i++){
            scanf("%d",a+i);
        }
        for(int i=1;i<=m;i++){
            scanf("%d",b+i);
        }
        for(int i=1;i<=k;i++){
            scanf("%d",c+i);
        }
        ll ans=1000000000005LL;
        int i=1,z=1;
        for(int j=1;j<=m;j++){
            while(i<n && a[i]<b[j]) i++;
            while(z<k && c[z]<b[j]) z++;
            ll k1=abs(a[i]-b[j]);
            ll k2=abs(c[z]-b[j]);
            if(i!=1){
                 k1=min(k1,abs((ll)b[j]-a[i-1]));
            }
            if(z!=1){
                k2=min(k2,abs((ll)c[z-1]-b[j]));
            }
            //cout<<j<<" "<<k1<<" "<<k2<<endl;
            ans=min(ans,k1+k2+3);
        }
        cout<<ans<<endl;
    }
    return 0;
}
F汤圆防漏理论
题目链接


http://www.bnuoj.com/v3/contest_show.php?cid=9358#problem/F


题目


Time Limit: 1000msMemory Limit: 262144KB 64-bit integer IO format: %lld Java class name: Main 
Submit Status PID: 53076 
ghc很喜欢吃汤圆,但是汤圆很容易被粘(zhān)漏。


根据多年吃汤圆经验,ghc总结出了一套汤圆防漏理论:


互相接触的汤圆容易粘(zhān)在一起,并且接触面积不同,粘(zhān)在一起的粘(nián)度也不同。


当ghc要夹起一个汤圆时,这个汤圆和现在碗里与这个汤圆接触的所有汤圆之间的粘(nián)度的和,如果大于汤圆的硬度,这个汤圆就会被粘(zhān)漏。


今天ghc又要煮汤圆啦,今天要煮n个汤圆,并且摆盘的方法已经设计好:


汤圆按照1, 2, \dots , n编号,有m对汤圆互相接触,用x_i, y_i, z_i表示编号为x_i和y_i的两个汤圆互相接触,粘(nián)度为z_i。


汤圆当然是越软越好吃,但是ghc的厨艺只允许把所有汤圆煮成同样的硬度。那么,汤圆的硬度最小可以是多少,可以满足吃的过程中,存在一种夹汤圆的顺序,使得没有汤圆会被粘(zhān)漏呢?


注意:


不考虑汤圆的重力作用;


不能同时夹多个汤圆;


吃完汤圆一定要喝点汤。


Input


第一行是一个正整数T(\leq 5),表示测试数据的组数,


对于每组测试数据,


第一行是两个整数n,m(1\leq n,m\leq 100000),


接下来m行,每行包含三个整数x_i, y_i, z_i(1\leq x_i, y_i \leq n, x_i \neq y_i, 1 \leq z_i \leq 1000000),


同一对汤圆不会出现两次。


Output


对于每组测试数据,输出一行,包含一个整数,表示汤圆硬度的最小值。


Sample Input



4 6 
1 2 2 
1 3 2 
1 4 2 
2 3 3 
2 4 3 
3 4 5


Sample Output


6


分析
这道题目思路没错,但是当时程序实现的有些问题

用一个有序集合set来维护汤圆,set里面存放一个二元组<粘度和,汤圆编号>。
每次取出set的第一个元素,也就是粘度和最小的汤圆,然后修改与该汤圆所有相接触的汤圆的粘度和。
set本身是不支持修改集合内元素的功能的,但是可以通过删除原有元素、插入修改后元素来实现。
只需遍历一遍汤圆和所有边,所有时间复杂度为O( (n+m)logn).
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+100;
typedef long long ll;
typedef pair<ll,ll> pa;
set<pa> s; //set自动排序,按第一个关键字
vector<pa> g[maxn];
ll nd[maxn]; //汤圆的粘度
ll vis[maxn];

void init()
{
    memset(nd,0,sizeof(nd));
    for(int i=0;i<maxn;i++) g[i].clear();
    s.clear();
    memset(vis,0,sizeof(vis));
}
int main()
{
    ll T;
    cin>>T;
    while(T--)
    {
        init();
        ll n,m;
        cin>>n>>m;
        for(int i=1;i<=m;i++)
        {
            int x,y,z;
            cin>>x>>y>>z;
            g[x].push_back(pa(y,z));
            g[y].push_back(pa(x,z));
            nd[x]+=z;
            nd[y]+=z;
        }
        //将所有汤圆放入集合
        for(int i=1;i<=n;i++) s.insert(make_pair(nd[i],i));

        //一个一个取汤圆
        ll ans=0;
        while(!s.empty())
        {
            set<pa>::iterator it;
            it=s.begin();
            pa now=*it;
            ans=max(ans,now.first);
            s.erase(it);
            //更新粘度
            ll u=now.second;
            vis[u]=1;
            for(ll i=0;i<g[u].size();i++)
            {
                ll v=g[u][i].first;
                if(vis[v]) continue;//只看父子边
                it=s.find(pa(nd[v],v));
                s.erase(it);
                nd[v]-=g[u][i].second;
                s.insert(pa(nd[v],v));
            }
        }
        cout<<ans<<endl;
    }
    return 0;
}

E可以来拯救吗

链接:https://www.nowcoder.com/acm/contest/117/E
来源:牛客网


题目描述 
quailty is BNU's red sun.
quailty非常喜欢给同学讲题,一天,他又拉着SK给他讲题。
quailty:给一个长为n的序列,求给定子序列的和,会吗?
SK:。。
quailty:给一个长为n的序列,求给定子序列的和的平方,会吗?
SK:。。。
quailty:给一个长为n的序列,求所有子序列的和的平方和,会吗?
SK:。。。。。
quailty:给一个长为n的序列,求所有长为k的子序列的和的平方和,会吗?
SK:。。。。。。。。
quailty:给一个长为n的序列,求所有长为k的子序列的和的平方的异或和,会...
SK:我^(^&*((^%^#……
SK拔出了他的40m长刀,场面就快控制不住了,请你赶快来做出这道题,拯救一下quailty。
quailty is BNU's red sun.
输入描述:
第一行是一个正整数T(≤ 10),表示测试数据的组数,


对于每组测试数据,


第一行是两个正整数n,k(k ≤ n ≤ 100000),分别表示序列长度和需要考虑的子序列长度,


接下来一行包含n个不超过1000的正整数,表示序列的n个元素,


为了简化问题,保证,也就是说,需要考虑的子序列不超过100000个。
输出描述:
对于每组测试数据,输出一行,包含一个整数,表示所有长为k的子序列的和的平方的异或和。
示例1
输入


1
4 2
1 2 3 4
输出

12
说明

对于样例,长度为2的子序列有[1,2],[1,3],[1,4],[2,3],[2,4],[3,4],所以答案是9^16^25^25^36^49=12(这里'^'是C++中的异或运算符)。

题意:给一个长为 n 的序列需要对所有长为 k 的子序列求和的平方的异或和,保证 Ck ≤ 105。
做法:dfs 枚举。一个 trick 是,n 和 k 都可能很大,比如
n = 100000, k = 99999,此时正常的枚举子序列会严重超时。解决办法是当 k ≥ n/2 时,令 k = n − k,然后枚举反面即可。

#include <cstdio>
#define LL long long
using namespace std;
const int N=100005;
LL ans,sum;int n,k,a[N];bool f;
void dfs(int t,int x,LL he)
{
    if (n-t+1<k-x) return;
    if (t>n || x==k)
    {
        if (x==k && f) ans^=(sum-he)*(sum-he);
        else if (x==k) ans^=he*he;
        return;
    }
    dfs(t+1,x,he);
    dfs(t+1,x+1,he+(LL)a[t]); 
}
int main()
{
    int T;scanf("%d",&T);
    while (T--)
    {
        ans=0;f=0;
        scanf("%d%d",&n,&k);if (k>n/2) k=n-k,f=1;sum=0;
        for (int i=1;i<=n;i++) scanf("%d",&a[i]),sum+=(LL)a[i];
        dfs(1,0,0);
        printf("%lld\n",ans);
    }
}


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值