2019-7-16考试总结

A. 礼物

 

概率期望。。。

对于每一个概率期望题,都可以把它看作一个有向无环图。

我们让目标状态$(11111……)$为起点,递推到起始状态$(00000……)$。

目标状态的期望步数为0。

比如,$111$ 可以 转移 $101$、$110$、$011$,

$F_{111}=F_{101} \times (1-P_2)+F_{101}\times P_2+1$,

它有$P_2$的概率转移到$111$,还有$1-P_2$的概率转移到自己。

所以$dp$式子就是 $F_s=\sum F_{s'}\times P_i+F_s\times (1-\sum P_i)$

把这个式子移项然后除过去就是答案。

基本上每一个概率期望题都是倒序$dp$,

但是也有$wd$大神用的正序。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#define Maxn 35
#define Reg register
using namespace std;
long long n,maxx,ans,sum,val[Maxn];
double l,sup,pv[Maxn],f[1<<20|1];
int main()
{
    scanf("%lld",&n);
    for(Reg int i=1;i<=n;++i)
    {
        scanf("%lf%lld",&pv[i],&val[i]);
        sup+=pv[i]; sum+=val[i];
    }
    printf("%lld\n",sum);
    for(Reg int i=1;i<(1<<n);++i)
    {
        l=0;
        for(Reg int j=1,t;j<=n;++j)
        {
            if(((1<<(j-1))&i)==0) continue;
            t=((1<<(j-1))^i);
            f[i]+=f[t]*pv[j];
            l=l+pv[j];
        }
        f[i]=(f[i]+1)/l;
    }
    printf("%0.3lf",f[(1<<n)-1]);
    return 0;
}
View Code

 

B. 通讯

 

这个题是一个$tarjan$板子+贪心。

首先用$tarjan$缩强联通分量,之后就变成了一个有向无环图。

之后就想到最小生成树?

$No$,最小生成树只能在无向图中存在,

而这个题告诉你整个图联通,那么只需要贪心找连接到每个点的最短的边就可以。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#define Maxn 50050
#define Reg register
using namespace std;
long long ans;
int n,m,tot,top,fic[Maxn],fat[Maxn],fir[Maxn],vis[Maxn];
int indx,cnt,low[Maxn],dfn[Maxn],stack[Maxn],ins[Maxn],pos[Maxn];
struct Tu {int st,ed,next; long long val;} lian[Maxn*10],liap[Maxn*10];
long long max(long long x,long long y) {return x>y?x:y;}
long long min(long long x,long long y) {return x<y?x:y;}
void add(int x,int y,long long z)
{
    lian[++tot].st=x;
    lian[tot].ed=y;
    lian[tot].val=z;
    lian[tot].next=fir[x];
    fir[x]=tot;
    return;
}
void adp(int x,int y,long long z)
{
    liap[++top].st=x;
    liap[top].ed=y;
    liap[top].val=z;
    liap[top].next=fic[x];
    fic[x]=top;
    return;
}
int get_fat(int x)
{
    if(fat[x]==x) return x;
    else return fat[x]=get_fat(fat[x]);
}
bool comp(Tu a,Tu b) {return a.val<b.val;}
long long min_tree()
{
    long long ans=0;
    sort(liap+1,liap+top+1,comp);
    for(Reg int i=0;i<=cnt;++i) fat[i]=i;
    for(Reg int i=1,x,y;i<=top;++i)
    {
        if(!vis[liap[i].ed])
        {
            vis[liap[i].ed]=1;
            ans+=liap[i].val;
        }
    }
    return ans;
}
void tarjan(int x)
{
    low[x]=dfn[x]=++indx;
    stack[++stack[0]]=x; ins[x]=1;
    for(Reg int i=fir[x];i;i=lian[i].next)
    {
        if(!dfn[lian[i].ed])
        {
            tarjan(lian[i].ed);
            low[x]=min(low[x],low[lian[i].ed]);
        }
        else if(ins[lian[i].ed])
            low[x]=min(low[x],dfn[lian[i].ed]);
    }
    if(low[x]==dfn[x])
    {
        ++cnt;
        while(stack[stack[0]]!=x)
        {
            ins[stack[stack[0]]]=0;
            pos[stack[stack[0]--]]=cnt;
        }
        ins[stack[stack[0]]]=0;
        pos[stack[stack[0]--]]=cnt;
        pos[x]=cnt;
    }
    return;
}
void init()
{
    ans=tot=top=indx=cnt=stack[0]=0;
    memset(fir,0,sizeof(fir));
    memset(fic,0,sizeof(fic));
    for(Reg int i=0;i<=n;++i)
        low[i]=dfn[i]=ins[i]=pos[i]=vis[i]=0;
    return;
}
int main()
{
//    freopen("text.in","r",stdin);
    while(scanf("%d%d",&n,&m)==2)
    {
        init();
        if(!n&&!m) return 0;
        for(Reg int i=1,x,y;i<=m;++i)
        {
            long long z;
            scanf("%d%d%lld",&x,&y,&z);
            add(x,y,z);
        }
        for(Reg int i=0;i<=n;++i)
            if(!dfn[i]) tarjan(i);
        for(Reg int i=1;i<=tot;++i)
        {
            if(pos[lian[i].st]!=pos[lian[i].ed])
                adp(pos[lian[i].st],pos[lian[i].ed],lian[i].val);
        }
        ans=min_tree();
        printf("%lld\n",ans);
    }
    return 0;
}
View Code

 

C. 奇袭

 

首先,最能想到的是$O(n^5)$的暴力,拿到$27$分,

之后用一个二维前缀和优化到$O(n^3)$,依然$27$分,

但是如果看到输入格式的最后一句话的话,可以优化到$O(n^2)$,拿到$64$分。

正解为$O(nlogn)$,$100$分

这个题正解是分治。

 

对于$n^2$的打法,题目说每一行每一列只能有$1$个军队,

我们把每个$y$纵坐标对应一个$x$横坐标,

$x$递增排列。

如果一个区间合法,那么肯定满足在这个区间中$max_y-min_y=r-l$,

那么枚举每个起点和终点,判断此区间是否合法。

复杂度$O(n^2)$。

 

对于$O(nlogn)$的打法,

每个区间二分为 左区间$l~mid$和右区间$mid+1~r$,

那么这个区间的总方案数就是 两个分区间的方案数$+$跨$mid$的合法方案数,

两个分区间的方案数可以直接加,重点是跨$mid$的合法方案数。

对于跨$mid$的合法方案数,可以分成$4$种情况:

$1$、最大值在左,最小值在右,

$2$、最大值在右,最小值在左,

$3$、最大值和最小值在左,

$4$、最大值和最小值在右,

前两种情况,我们要满足 $max_y-min_y=r-l$,

我们对这个式子移项

第$1$种情况为 $max_l-min_r=r-l$,即 $max_l+l=min_r+r$,

还要满足 最大值在左,最小值在右,具体实现可以自己思考。

第$2$种情况类似。

后两种情况就比较简单,只要在左(或右)区间找到$max$和$min$,那么区间长度就确定,

对应的在对面的区间找到对应的位置,判断是否合法即可。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#include<map>
#define Maxn 50050
#define INF 0x7fffffff
#define New new Tree
#define Reg register
using namespace std;
map<int,int> q;
int n,maxx,minn,mup,pos[Maxn],ls1[Maxn],ls2[Maxn],vis[Maxn];
struct Tree {Tree *lch,*rch; int val;};
struct comp
{
    bool operator () (int x,int y)
    {return ls1[x]>ls1[y];}
};
priority_queue<int,vector<int>,comp> p;
Tree *root=New();
void erfen(Tree *root,int l,int r)
{
    if(l==r)
    {
        root->val=1;
        return;
    }
    int mid=(l+r)/2;
    if(l<=mid)
    {
        root->lch=New();
        erfen(root->lch,l,mid);
    }
    if(mid+1<=r)
    {
        root->rch=New();
        erfen(root->rch,mid+1,r);
    }
    root->val=root->lch->val+root->rch->val;
    minn=INF,maxx=-INF;
    for(Reg int i=mid;i>=l;--i)
    {
        minn=min(minn,pos[i]);
        maxx=max(maxx,pos[i]);
        ls1[i]=maxx,ls2[i]=minn;
    }
    minn=INF,maxx=-INF;
    for(Reg int i=mid+1;i<=r;++i)
    {
        minn=min(minn,pos[i]);
        maxx=max(maxx,pos[i]);
        ls1[i]=maxx,ls2[i]=minn;
    }
    minn=INF,maxx=-INF;
    mup=mid+1;
    while(!p.empty()) p.pop();
    q.clear();
    for(Reg int i=mid+1;i<=r;++i)
    {
        minn=min(minn,pos[i]);
        maxx=max(maxx,pos[i]);
        for(Reg int j=mup-1;j>=l;--j)
        {
            if(ls2[j]>minn)
            {
                mup=j; p.push(j);
                if(!q[ls1[j]+j]) q[ls1[j]+j]=1;
                else ++q[ls1[j]+j];
            }
            else break;
        }
        while(!p.empty()&&ls1[p.top()]<maxx)
        {
            --q[ls1[p.top()]+p.top()];
            p.pop();
        }
        if(q[minn+i]) root->val+=q[minn+i];
    }
    minn=INF,maxx=-INF;
    while(!p.empty()) p.pop();
    q.clear();
    mup=mid;
    for(Reg int i=mid;i>=l;--i)
    {
        minn=min(minn,pos[i]);
        maxx=max(maxx,pos[i]);
        for(Reg int j=mup+1;j<=r;++j)
        {
            if(ls2[j]>minn)
            {
                mup=j; p.push(j);
                if(!q[ls1[j]-j]) q[ls1[j]-j]=1;
                else ++q[ls1[j]-j];
            }
            else break;
        }
        while(!p.empty()&&ls1[p.top()]<maxx)
        {
            --q[ls1[p.top()]-p.top()];
            p.pop();
        }
        if(q[minn-i]) root->val+=q[minn-i];
    }
    minn=INF,maxx=-INF;
    for(Reg int i=mid,k,ed;i>=l;--i)
    {
        maxx=max(maxx,pos[i]);
        minn=min(minn,pos[i]);
        k=maxx-minn+1;
        ed=i+k-1;
        if(ed<mid+1||ed>r) continue;
        if(ls1[ed]>maxx||ls2[ed]<minn) continue;
        ++root->val;
    }
    minn=INF,maxx=-INF;
    for(Reg int i=mid+1,k,ed;i<=r;++i)
    {
        maxx=max(maxx,pos[i]);
        minn=min(minn,pos[i]);
        k=maxx-minn+1;
        ed=i-k+1;
        if(ed>mid||ed<l) continue;
        if(ls1[ed]>maxx||ls2[ed]<minn) continue;
        ++root->val;
    }
    return;
}
int main()
{
    scanf("%d",&n);
    for(Reg int i=1,x,y;i<=n;++i)
    {
        scanf("%d%d",&x,&y);
        pos[x]=y;
    }
    erfen(root,1,n);
    printf("%d",root->val);
    return 0;
}
View Code

 

 

总结

首先看了$T1$,看了大概$10$分钟,跳到了第$2$题,发现$T2$是个板子,

赶紧码完$T2$,打完$tarjan$,最后就是如何处理跑遍全图路径最短,

首先想到了最小生成树,模了几个样例发现都过得去,就打了一遍克鲁斯卡尔,

考试之后才发现这最小生成树只能在无向图中用,$T2$只拿了$10$分。

读了$T3$一遍题,码了一遍$n^5$的暴力,这个东西可以用二维前缀和维护,

从$n^5$降到了$n^3$。应该能拿$30%$的分

没有看到输入格式的最后一句话……

最后回去想了一下$T1$,概率期望。。

感觉这东西一定是状压,但是不怎么会转移,惊喜地看到数据范围,有$10$分的送分点。

所以最后 $10+10+27=47$。

posted @ 2019-07-18 11:34  Milk_Feng  阅读(124)  评论(0编辑  收藏  举报