2017.9.10 机房模拟赛

这里写图片描述
这里写图片描述
这里写图片描述

这里写图片描述
这里写图片描述


貌似今天的题很水?然而我还是被虐?


t1 有个不等式,若 a>bgcd(a,b)<=abaxorb>=ab
然后就可以瞎搞了,这里粗略证明一下这两个不等式
如果我们有两个数 a,b ,令 a>b
如果 gcd(a,b)>ab ,两边同时除以 gcd(a,b) 并移项
a/gcd(a,b)1<b/gcd(a,b)
也就是说当 a 是整数,b是整数, a/gcd(a,b) 是整数, b/gcd(a,b) 是整数的时候,因为 a>b ,则 a/gcd(a,b)>b/gcd(a,b) ,然而 a/gcd(a,b)1 却又 <b/gcd(a,b) <script type="math/tex" id="MathJax-Element-4805"> 然后证明
axorb>=ab ,同样的,我们令a>b,则思考一个问题,如果我们进行二进制减法的话,什么时候会减下来,如果两位是一样的,我们显然是不需要减的,如果两位不一样的话,我们的答案会比a小,然而如果是xor操作,这一位又会和其一样,什么意思呢?我们举个例子

A:1 1 0 1 1
B:1 0 1 1 0
现在A数和B数进行减法,我们减的时候,如果A这一位是1,B这一位是0,那么异或起来和减起来的结果是一样的,如果A这一位是1,B这一位是1,异或和减的操作是一样的,如果A这一位是0,B这一位是0,那么异或和减的操作得到的结果是一样的,然而呢,如果A这一位是0,B这一位是1呢,我们发现A会往上借位,而异或操作不需要往上借位,这一位赋为1
我们如果从高位往低位看的话,如果两位都是1的话,显然异或操作和减的操作都是一样的,如果A是1,B是0或者A是0,B是0的情况两种操作得到的结果都是一样的,然而如果A是0,B是1的话,减法的时候会让已经算好的高位减1,用于借位,然而异或操作就不用,如果之前的都是一样的,那么在这里异或操作就比减的操作大了,所以证毕


代码:

#include<iostream>
#include<cstring>
#include<cstdio>
int a=0,ans=0,n;
int main(){
    scanf("%d",&n);
    for(register int c=1;c<=n;c++){//枚举c 
        for(register int j=2;j<=n/c;j++){//枚举a 
            a=c*j;
            if((a^c)==a-c) ans++;
        }
    }
    printf("%d\n",ans);
    return 0;
} 

t2一看就是点分题,这种送命题,一看就是我不会的点分知识,据说是没穿衣服的点分题,所以在这里我就先发代码,然后把代码注释了,再写少许题解


#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define LL long long
using namespace std;
const int MAXN=100010;
int n,S,E,core,tail=0,sum;
int head[MAXN],sz[MAXN],mx[MAXN],vis[MAXN];
LL ans=2e17,dis[MAXN];

struct Line{
    int to,nxt;
    LL flow;
}line[MAXN*4];

struct Tree{
    LL dis;int tp;
}a[MAXN];

bool cmp(Tree a,Tree b){return a.dis<b.dis;} 

void add_line(int from,int to,LL flow){
    line[++tail].to=to;
    line[tail].nxt=head[from];
    line[tail].flow=flow;
    head[from]=tail;
}
void getcore(int u,int fa){
    sz[u]=1;//这个点的size为1,之后再累加,表示当前这棵分治出来的树中他的sz为1
    for(register int i=head[u];i;i=line[i].nxt){
        int v=line[i].to;
        if(vis[v]||v==fa) continue;//vis表示这个点有没有作为分治中心被处理过
        getcore(v,u);//继续往下dfs
        sz[u]+=sz[v];
        mx[u]=max(sz[v],mx[u]);//找出每个点的最大的子树的大小
    }
    mx[u]=max(mx[u],sum-sz[u]);//比较除了这个最大的子树的最大的孩子的sz之外的大小
    if(mx[u]<mx[core]) core=u;//最大的部分最小,这个点就越是树的中心
}
void getdeep(int u,int fa,int sroot){//sroot表示从当前分治中心出来的第一个点,也就是和当前的分治中心相连接的那个点,所有该点的孩子的tp值都是这个点的编号
    for(register int i=head[u];i;i=line[i].nxt){
        int v=line[i].to;
        if(vis[v]||v==fa) continue;//如果已经被处理过我们就不再处理
        dis[v]=dis[u]+line[i].flow;//求距离
        if(sroot==0) getdeep(v,u,v);
        else         getdeep(v,u,sroot);
    }
    a[++tail].tp=sroot;//把点的tp值和点的dis值放入一个结构体中,在count函数中我们会进一步处理
    a[tail].dis=dis[u];
}
void count(){
    sort(a+1,a+tail+1,cmp);//排序
    int l=1,r=tail;//two-pointer
    while(l<r){
        LL k=a[l].dis+a[r].dis;//当前的两点的距离
        if(a[l].tp==a[r].tp){//如果两个点在同一边,我们不处理
            if(a[l].dis+a[r-1].dis<S) ++l;//此时如果距离小了我们++l
            else                      --r;//如果距离大了--r
        }else{
            if(k>=S) ans=min(ans,k),--r;//否则就是我们要统计的答案,此时如果距离够了,我们就统计最小距离,然后--r
            else                    ++l;//如果距离不够我们就要++l
        }
    }
}
int solve(int u,int fa){
    vis[u]=1;dis[u]=0;tail=0;//这个点已经被当做分治中心处理过了,现在我们需要处理所有经过这个点的路径的长度,必须要经过这个点,我们以这个点为中心,让这个点的dis为0,然后对于其他点我们分散地去处理
    getdeep(u,fa,0);//这里我们处理其他的所有的点,处理所有点到现在的分治中心的距离
    count();//然后进行two pointer的计数
    for(register int i=head[u];i;i=line[i].nxt){
        int v=line[i].to;//对所有的u的孩子所在的子树进行分治
        if(vis[v]||v==fa) continue;
        core=0;sum=sz[v];//先找到u的孩子v所在的子树的分治中心,然后找到这个中心,整个子树的size也就是sum为v的size
        getcore(v,u);solve(core,u);//然后以这个新的分治中心为中心进行点分
    }
}
int main(){
    scanf("%d%d%d",&n,&S,&E);
    for(register int i=1;i<n;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        add_line(u,v,w);add_line(v,u,w);
    }//建边
    core=0;mx[core]=0x3fffffff;sum=n;//先令根节点(点分中心,树的中心)为0,然后该树的mx值为0x3fffffff表示这个点的子树大小是无穷大
    getcore(1,0);//先找当前这棵树的重心
    solve(core,0);//对于当前的树的重心我们进行递归的分治处理
    if(ans>E) cout<<-1<<endl;//找出的ans如果比E大说明没有解
    else      cout<<ans<<endl;//否则输出
    return 0;
}

貌似题解t2的题解都写在代码里了?
那我再发一份貌似写挂了的 n2 代码吧

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
const int MAXN=100000*2;
vector<long long>v;
struct Line{
    int to,nxt;
    long long flow;
}line[MAXN*2];
int head[MAXN],tail,dep[MAXN],top[MAXN],fa[MAXN],sz[MAXN],son[MAXN],N,S,E,ff,tt;
long long cnt[MAXN],fe;
void add_line(int from,int to,long long flow){
    line[++tail].to=to;
    line[tail].flow=flow;
    line[tail].nxt=head[from];
    head[from]=tail;
}
void dfs(int u,int fat,long long num,int dp){
    cnt[u]=num;
    dep[u]=dp;
    fa[u]=fat;sz[u]=1;
    for(register int i=head[u];i;i=line[i].nxt){
        int v=line[i].to;
        if(v==fat) continue;
        dfs(v,u,num+line[i].flow,dp+1);
        sz[u]+=sz[v];
        if(son[u]==-1||sz[son[u]]<sz[v]) son[u]=v; 
    }
}
void dfs2(int u,int tp){
    top[u]=tp;
    if(son[u]==-1) return;
    dfs2(son[u],tp);
    for(register int i=head[u];i;i=line[i].nxt){
        int v=line[i].to;
        if(son[u]==v||v==fa[u]) continue;
        dfs2(v,v);
    }
}
int lca(int x,int y){
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        x=fa[top[x]];
    }
    return dep[x]<dep[y]?x:y;
}
int main(){
    freopen("path.in","r",stdin);
    freopen("path.out","w",stdout);
    memset(son,-1,sizeof(son));
    scanf("%d%d%d",&N,&S,&E);
    for(register int i=1;i<=N-1;i++){
        scanf("%d%d%I64d",&ff,&tt,&fe);
        add_line(ff,tt,fe);
        add_line(tt,ff,fe);
    }
    dfs(1,-1,0,0);
    dfs2(1,1);
    for(register int i=1;i<=N;i++){
        for(register int j=1;j<=N;j++){
            int faa=lca(i,j);
            v.push_back(cnt[i]+cnt[j]-2*cnt[faa]);
        }
    }
    sort(v.begin(),v.end());
    if(S>v[v.size()-1]){
        printf("-1\n");return 0;
    }
    int loc=lower_bound(v.begin(),v.end(),S)-v.begin();
    if(v[loc]<=E)printf("%I64d",v[loc]);
    else         printf("-1\n");
    return 0;
}

t3显然是一道费用流,类似之前做的修车那道题,那道题之前也写了博客,就是倒数第几个进某个洞对总时间的贡献。
也就是说我们首先把源点连向n个鸟巢???然后S到每种鸟巢的流量为该种鸟的数量,费用为0,记所有的鸟的数量为all,把所有的洞拆成all*m个,其中第m个洞的第1个洞表示倒数第一个经过这个洞,第m个洞的第2个洞表示倒数第二个经过这个洞,显然我们可以把每一只鸟连向每个拆出来的洞,表示这只鸟可以走某个洞的倒数第几个,然后只需要把每个拆分出来的点连向T就可以了,流量为1,费用为0,也就是说,只有一只鸟可以从某个洞的倒数第几个洞走出去。
我们从鸟到洞连的边权为这只鸟在这个洞(此时这个洞已经被赋予[已拆分]了倒数第几个出去)飞出去对答案的贡献,如果我们这只鸟是倒数第一个飞出去的,那么对答案的贡献显然是这只鸟飞过这个洞的时间,如果这只鸟是倒数第二个飞出去的,那么对答案的贡献显然是这只鸟飞过这个洞的时间乘2,原因是既然这只鸟事倒数第二个飞出去的,那么倒数第一只鸟必须得等你这只鸟飞出去了之后才能飞,所以倒数第一只鸟也要等这只鸟飞的时间。
然而这道题的数据规模太大了,如果我们直接连边的话,然后再跑费用流会发现边数太多了,于是我们用动态加边的方式进行加边,原因及正确性其实很简单,因为我们最小费用最大流的原理是每次都找一条费用最小的路进行增广,所以如果我们把所有的图建出来之后再去跑,其实每次增广的时候如果要增广某只鸟从某个洞的倒数第二个出去,那么一定会先增广倒数第一个出去,因为倒数第一个出去花费更少,我们SPFA的时候会先增广到短的那条路径。
所以我们只需要把每次增广的时候的最后一个点找出来,然后去对这个点+1然后表示倒数第多一次出去,然后建边即可,这样会省很多时间。
如果还有什么不懂的,请联系
邮箱:2632812444@qq.com
邮箱:heisenbergchengdu@foxmail.com
邮箱:cdqzhhl@gmail.com


#include<iostream> 
#include<cstdio> 
#include<cstring> 
#include<algorithm> 
#include<queue>
using namespace std; 
const int MAXN=500000+1000; 
struct Line{int to,nxt,flow,cost;}line[MAXN*2];
int head[MAXN],tail=1,last[MAXN],S,T,dis[MAXN],ans,n,all,m,p[MAXN];
int t[105][105];
bool vis[MAXN];
void add_line(int from,int to,int flow,int fee){
    line[++tail].to=to;line[tail].nxt=head[from];line[tail].flow=flow;line[tail].cost=fee;head[from]=tail;
    line[++tail].to=from;line[tail].nxt=head[to];line[tail].flow=0;line[tail].cost=-fee;head[to]=tail;
}
bool SPFA(){
    for(register int i=S;i<=T;i++) dis[i]=0x7ffffff;
    queue<int>q;
    while(!q.empty())q.pop();
    vis[S]=true;dis[S]=0;q.push(S); 
    while(!q.empty()){
        int u=q.front();vis[u]=false;q.pop();
        for(register int i=head[u];i;i=line[i].nxt){
            int v=line[i].to;
            if(line[i].flow&&dis[v]>dis[u]+line[i].cost){
                dis[v]=dis[u]+line[i].cost;last[v]=i;
                if(!vis[v]){
                    vis[v]=true;
                    q.push(v);
                }
            }
        }
    }
    if(dis[T]!=0x7ffffff) return true;
    else                  return false;
}
void add(){
    int i=last[T],floww=0x7fffffff;
    for(;i;i=last[line[i^1].to])
    floww=min(floww,line[i].flow);
    ans+=floww*dis[T];
    i=last[T];
    for(;i;i=last[line[i^1].to]){
        line[i].flow-=floww;
        line[i^1].flow+=floww;
    }
    int now=line[last[T]^1].to;//all*(p-1)+tmp+n 
    int x=(now-n)/all,y=(now-n)%all;
    if(y==0) return;
    add_line(now+1,T,1,0); 
    for(register int i=1;i<=n;i++) add_line(i,now+1,1,(y+1)*t[i][x+1]);
}
int main(){
    scanf("%d%d",&n,&m);S=0;
    for(register int i=1;i<=n;i++){
        scanf("%d",&p[i]);all+=p[i];add_line(S,i,p[i],0); 
    }T=all*m+n+1;
    for(register int i=1;i<=n;i++)
        for(register int j=1;j<=m;j++)
            scanf("%d",&t[i][j]);
    for(register int i=1;i<=n;i++)
        for(register int j=1;j<=m;j++)
            add_line(i,n+(j-1)*all+1,1,t[i][j]);
    for(register int i=1;i<=m;i++) add_line(n+(i-1)*all+1,T,1,0);
    while(SPFA())
        add();
    printf("%d\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值