2018中国大学生程序设计竞赛网络选拔赛

史上体验最差比赛

快速做出04交上去 , 3小时出结果 , 还WA了 , 要是服务器正常一小时就可以出了 . 导致我其他题看到一半还要回来重新看一遍04代码 , 节奏直接崩了 , a掉都4个小时了 . 不过队友那边卡在09了 , 那题应该是组合数学的题 , 结果我在看03那道题目包装到过分发题目 , 看了半天愣是没看懂 . 最后只能转到最后一题 , 一个树状数组的水题 , 结果我队友敲完还在debug的时候就结束了

总结一下我自身的原因 , 我虽然数学知识学的挺多 , 但是实际运用却比较少 , 导致包装有点多的数学题就不会从数学的角度去思考 . 而且有时候懒得打表…

所以我觉得我可以暂时放一下新的知识 , 把接下来的时间放在”温故”上


L4. Find Integer

找到b,c满足 an+bn=cn a n + b n = c n

费马大定理 , n>2时无非零解 , n=2时有
这里写图片描述

#include<bits/stdc++.h>
using namespace std;
#define sf(s) scanf("%d",&s)


int main(){
    int t;sf(t);
    while(t--){
        int n,a; sf(n),sf(a);
        if(n==0||n>2)printf("%d %d\n",-1,-1);
        else if(n==1)printf("%d %d\n",1,a+1);
        else {
            if(a%2){
                printf("%d %d\n",a*a/2,a*a/2+1);
            }
            else{
                printf("%d %d\n",a*a/4-1,a*a/4+1);
            }
        }
    }
}

L9. Tree and Permutation

给一棵n节点树 , 边权为正 , 对于n的全排列中的每个序列 , 走一遍(1324代表1->3->2->4,树上两点间路径唯一)
求走过的边权和%1e+7

对于每条边e(边权为v)分析 , 假设i和j分别在e的两边 , 那么ij在排序时在一起时 , 会走过e一次

那么假设e左边点数M , 另一边就是n-M , 方案数为 M(nM) M ∗ ( n − M ) , ij在排序中在一起的方案数为 2(n1)! 2 ∗ ( n − 1 ) ! , 所以e对答案的贡献为 M(nM)2(n1)!v M ∗ ( n − M ) ∗ 2 ∗ ( n − 1 ) ! ∗ v


#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef long long ll;
typedef long long D;
const LL mod=1e9+7;
D read(){ D ans=0; char last=' ',ch=getchar();
while(ch<'0' || ch>'9')last=ch,ch=getchar();
while(ch>='0' && ch<='9')ans=ans*10+ch-'0',ch=getchar();
if(last=='-')ans=-ans; return ans;
}
#define debug(i) printf("-> %d\n",i)

const int N=400009;
int head[N],to[N],v[N],nex[N],now;
int siz[N];

LL fac[N];
void init(){
    fac[0]=fac[1]=1;
    for(int i=2;i<N-3;i++)fac[i]=fac[i-1]*i%mod;
}

int dfs(int p,int fa){
    siz[p]=1;
    for(int i=head[p];~i;i=nex[i]){
        if(to[i]==fa)continue;
        siz[p]+=dfs(to[i],p);
    }
    return siz[p];
}

void get(int n,int p,int fa,LL &ans){
    for(int i=head[p];~i;i=nex[i]){
        int u=to[i];
        if(u==fa)continue;
        int M=siz[u];
        LL add=2ll*M*(n-M)%mod*v[i];
        ans=(ans+add)%mod;
        get(n,u,p,ans);
    }
}

int main(){
    init();
    int n;
    while(cin>>n){
        now=0;memset(head,-1,sizeof(head));
        memset(siz,0,sizeof(siz));
        for(int i=1;i<n;i++){
            int a,b,vv;
            scanf("%d%d%d",&a,&b,&vv);
            to[++now]=b,v[now]=vv,nex[now]=head[a],head[a]=now;
            to[++now]=a,v[now]=vv,nex[now]=head[b],head[b]=now;
        }
        dfs(1,-1);//debug(1);
        LL ans=0;
        get(n,1,-1,ans);
        ans=(ans*fac[n-1])%mod;
        printf("%lld\n",ans);
    }
}

L10. YJJ’s Salesman

开始在(0,0) , 你可以往右 , 往下或往右下走一格 , 图中有n个点(x,y) , 价值v , 当你从(x-1,y-1)走到(x,y)时 , 你会获得v , 求获得的最大价值

也就是说 , 要想得到(x,y)的价值 , 你需要在 (a<x,b<y) ( a < x , b < y ) 才行 , 那么我可以排序x轴 , 离散化y , 按照y轴建树状数组 , 走到了y时 , 取1~y-1的最大值加上当前的价值去更新y以上的数组(因为只有y以上才能由y转移)

当然树状数组要从y开始更新而不是y+1 , 因为y+1是可以取到y的值的 , 你从y+1开始更新就会对y+1的点造成错误


#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N=1e5;

struct node{
    int x,y,v;
    bool operator<(const node a)const{
        if(x==a.x)return y>a.y;
        return x<a.x;
    }
}e[N+9];
int id[N+9];

int n;
int tr[N+9];
void up(int p,int v){
    while(p<=n){
        tr[p]=max(tr[p],v);
        p+=p&(-p);
    }
}

int que(int p){
    int ans=0;
    while(p>=1){
        ans=max(ans,tr[p]);
        p-=p&(-p);
    }return ans;
}

int main(){
    int t;scanf("%d",&t);
    while(t--){
        memset(tr,0,sizeof(tr));
        n;scanf("%d",&n);
        for(int i=1;i<=n;i++){
            scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].v);id[i]=e[i].y;
        }
        sort(id+1,id+1+n);
        int siz=unique(id+1,id+1+n)-id;
        for(int i=1;i<=n;i++)
            e[i].y=lower_bound(id+1,id+siz+1,e[i].y)-id;
        sort(e+1,e+1+n);
        int ans=0;
        for(int i=1;i<=n;i++){
            int p=e[i].y,v=e[i].v;
            int add=que(p-1);
            ans=max(ans,add+v);
            up(p,add+v);
        }
        printf("%d\n",ans);
    }
}

L3. Dream

题目太长了就讲一下主要的东西吧

要求重写加法和乘法 , 满足 mp+np=(m+n)p m p + n p = ( m + n ) p 对于任意n,m小于p成立

由费马小定理得 :

ap11(modp) a p − 1 ≡ 1 ( m o d p )
apa(modp) a p ≡ a ( m o d p )
(a+b)pa+b(modp) ( a + b ) p ≡ a + b ( m o d p )

所以把 a+b(a+b)%p,ab(ab)%p a + b → ( a + b ) % p , a ∗ b → ( a ∗ b ) % p


int main(){
    int t;scanf("%d",&t);
    while(t--){
        LL p;scanf("%lld",&p);
        for(LL i=1;i<=p;i++){
            for(LL j=1;j<=p;j++){
                printf("%lld%c",(i+j-2)%p,j==p?'\n':' ');
            }
        }
        for(LL i=1;i<=p;i++){
            for(LL j=1;j<=p;j++){
                printf("%lld%c",(i-1)*(j-1)%p,j==p?'\n':' ');
            }
        }
    }
}

L1. Buy and Resell

题意 :

有一条直线上n个点的价值 ai a i , 你的初始能量值为0 , 你从一端走到令一端 , 每个点都可以花费其价值获得一能量 , 或是花费一能量得到其价值 , 也可以什么都不做
求可以获得的最大价值

解析 :

每种物品都有3种状态,买,放置,买后卖出

我们对于每件物品默认先买掉即获得价值 , 并往优先队列中塞两个负的价值

当当前物品(B)为最小价值时,会pop掉一个自己,所以留下一个自己,对答案贡献x-x,相当于放置

如果不是最小价值,会pop掉前面的最小的一个(A),在此时减掉前面的价值A,加上自己的价值B,相当于买入当前卖掉前面,此时队列留下两个

为什么留下两个 ? 因为如果此物品需要卖 , 那么这两个都要pop即x-2*x , 在加入的时候pop掉了A , 加上自己的B , 所以留下两个

#include<bits/stdc++.h>
using namespace std;
#define mk make_pair
#define pill pair<int,int>
#define LL long long

int read(){
    int ans=0;char l=' ',c=getchar();
    while(!isdigit(c))l=c,c=getchar();
    while(isdigit(c))ans=ans*10+c-'0',c=getchar();
    if(l=='-')ans=-ans;return ans;
}

int main(){
    int t=read();
    while(t--){
        LL ans=0,ct=0;
        int n=read();
        priority_queue<pill>q;//价值小的优先卖出
        while(n--){
            int item=read();
            q.push(mk(-item,1));//pop时代表:买入当前物品卖出x
            q.push(mk(-item,2));//pop时代表:卖出x,相当于反悔买入x
            pill p=q.top();q.pop();
            int f=p.first,s=p.second;
            ans+=(LL)item+f;//默认先买入x
            if(s==1)ct++;//对于实际交易,买入一次必对应一次卖出,所以输出时乘2
        }
        printf("%lld %lld\n",ans,ct<<1);
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值