USACO2018 DEC(Platinum) (树上乱搞,期望+凸包)

发现这跟\(Gold\)难度简直天差地别啊。。

\(T1\)

传送门

解题思路

  这道题还是很可做的,发现题意可以传化成一棵树每次从叶子节点删边,然后有\(m\)条限制,形如\(a\)\(b\)前面删去。发现\(a\)\(b\)前面删去其实就是\(ban\)掉一棵树上一段或两段连续的\(dfs\)序,这个讨论一下关系即可。这样就可以树剖+线段树维护,但是直接做只有\(88\)分,因为没有考虑不合法的情况,判不合法的情况可以根据删除关系建一张有向图,然后最后判断有无环即可。

代码

#include<bits/stdc++.h>
 
using namespace std;
const int N=100005;
 
inline int rd(){
    int x=0,f=1; char ch=getchar();
    while(!isdigit(ch)) f=ch=='-'?0:1,ch=getchar();
    while(isdigit(ch)) x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
    return f?x:-x;
}
 
int n,m,num,siz[N],top[N],son[N],id[N],fa[N],dep[N],f[N][20];
int vis[N],tot;
bool use[N],tt[N];
vector<int> v[N],G[N];
 
struct Segment_Tree{
    #define mid ((l+r)>>1)
    int sum[N<<2],tag[N<<2];
    void pushdown(int x){
        sum[x<<1]|=tag[x];
        sum[x<<1|1]|=tag[x];
        tag[x<<1]|=tag[x];
        tag[x<<1|1]|=tag[x];
        tag[x]=0;
    }
    void update(int x,int l,int r,int L,int R){
        if(L>R) return ;
        if(L<=l && r<=R) {sum[x]=tag[x]=1; return;}
        if(tag[x]) pushdown(x);
        if(L<=mid) update(x<<1,l,mid,L,R);
        if(mid<R) update(x<<1|1,mid+1,r,L,R);
        sum[x]=(sum[x<<1]|sum[x<<1|1]);
    }
    int query(int x,int l,int r,int pos){
        if(l==r) return sum[x];
        if(tag[x]) pushdown(x);
        if(pos<=mid) return query(x<<1,l,mid,pos);
        return query(x<<1|1,mid+1,r,pos);
    }
    #undef mid
}tree;
 
void dfs1(int x,int F){
    siz[x]=1; fa[x]=F; int maxson=-1;
    f[x][0]=F;
    for(int i=1;i<=17;i++)
        f[x][i]=f[f[x][i-1]][i-1];
    for(int i=0;i<v[x].size();i++){
        int u=v[x][i]; if(u==F) continue;
        dep[u]=dep[x]+1; dfs1(u,x); siz[x]+=siz[u];
        if(siz[u]>maxson) maxson=siz[u],son[x]=u;
    }
}
 
void dfs2(int x,int topf){
    top[x]=topf; id[x]=++num;
    if(!son[x]) return;
    dfs2(son[x],topf); int u;
    for(int i=0;i<v[x].size();i++){
        u=v[x][i]; if(u==fa[x] || u==son[x]) continue;
        dfs2(u,u);
    }
}
 
int jump(int x,int D){
    for(int i=17;~i;i--)
        if(dep[f[x][i]]>=D) x=f[x][i];
    return x;
}
 
void dfs(int x,int F){
    use[x]=1;
    for(int i=0;i<v[x].size();i++){
        int u=v[x][i]; if(u==F || use[u]) continue;
//      cout<<u<<" "<<x<<endl;
        G[u].push_back(x); dfs(u,x);
    }
}
 
void cir(){
    for(int i=1;i<=n;i++) puts("0");
    exit(0);
}
 
void find_cir(int x){
    for(int i=0;i<G[x].size();++i){
        int u=G[x][i]; 
        if(vis[u]) cir();
        if(tt[u]) continue;
        vis[u]=1; tt[u]=1;
        find_cir(u); vis[u]=0;
    }
}   
 
int main(){ 
//  freopen("in","r",stdin);
    n=rd(),m=rd(); int x,y;
    for(int i=1;i<n;i++){
        x=rd(),y=rd();
        v[x].push_back(y);
        v[y].push_back(x);
    }
    dfs1(1,0); dfs2(1,1);
    while(m--){
        x=rd(),y=rd(); G[x].push_back(y);
//      cout<<x<<" "<<y<<endl;
        if(id[y]>=id[x] && id[y]<=id[x]+siz[x]-1){
            int now=jump(y,dep[x]+1);
            tree.update(1,1,n,1,id[now]-1);
            tree.update(1,1,n,id[now]+siz[now],n);
            G[x].push_back(now);
//          cout<<x<<" "<<now<<endl;
            for(int i=0;i<v[x].size();i++){
                int u=v[x][i]; 
                if(id[u]>=id[now] && id[u]<=id[now]+siz[now]-1) continue;
//              cout<<u<<" "<<x<<endl;
                G[u].push_back(x); dfs(u,x);
            }
        }
        else{
            tree.update(1,1,n,id[x],id[x]+siz[x]-1);
            dfs(x,fa[x]);
        }
    }
    for(int i=1;i<=n;i++)
        if(!tt[i]) find_cir(i);
    for(int i=1;i<=n;i++) {
        if(!tree.query(1,1,n,id[i])) puts("1");
        else puts("0");
    }
    return 0;
}


\(T3\)

传送门

解题思路

  神仙题。首先发现对于一个点来说它的答案一定是从自己这个点直接跳下去或者向左右两边分别找到一个点跳下去。考虑区间\([0,L]\),设\(x\)走到\(L\)的概率为\(f(x)\),那么有\(f(x)=\frac{f(x-1)+f(x+1)}{2}\),发现这是个等差数列的形式,又因为\(f(L)=1,f(0)=0\)可以解得\(f(x)=\frac{x}{L}\),那么这个人在某个点往左右两边走时左边到达\(a\)点,右边到达\(b\)点,则他获得的收益为\(V_a \frac{b-i}{b-a}+V_b \frac{i-a}{b-a}\),那么就得到一个暴力的做法,就是枚举左右端点算最大值。但发现这样复杂度太高,考虑如何能快速找到左右两点。如果把它们放到平面直角坐标系上,每个点横坐标设为位置,纵坐标设为该位置的权值。那么画个图带进去可以发现,要求的其实就是两点连线与\(x=i\)之间交点的纵坐标,那么确定这两个点就可以用凸包判断了。就是把凸包找出来,然后对于一个点来说如果它在凸包上,答案就为这个点的纵坐标,否则为它前后两个凸包上的点连线的\(x=i\)时的纵坐标。

代码

#include<bits/stdc++.h>
 
using namespace std;
const int N=100005;
const double eps=1e-8;
typedef long long LL;
 
int n,stk[N],top;
double a[N];
  
struct Node{
    int x; LL y;
    Node(int _x=0,LL _y=0){
        x=_x; y=_y;
    }
}node[N];
 
Node operator-(Node A,Node B){
    return Node(A.x-B.x,A.y-B.y);
}
inline LL cross(Node A,Node B){
    return A.x*B.y-A.y*B.x;
}   
 
inline bool check(int x,int y,int z){
    return cross((node[y]-node[x]),(node[z]-node[y]))>=eps;
}
 
inline LL calc(int x,int y,int z){  
    return (node[y].y*(node[z].x-node[x].x)+node[x].y*(node[y].x-node[z].x))/(node[y].x-node[x].x);
}
  
int main(){
    scanf("%d",&n); double tmp;
    for(int i=1;i<=n;i++) {
        node[i].x=i; scanf("%lf",&tmp);
        node[i].y=tmp*100000;
    }   
    stk[++top]=0; node[n+1].x=n+1;
    for(int i=1;i<=n+1;i++){
        while(top>1 && check(stk[top-1],stk[top],i)) top--;
        stk[++top]=i;
    } int now=1;
    for(int i=1;i<=n;i++){
        while(stk[now]<i) now++;
        if(stk[now]==i) printf("%lld\n",(LL)(node[i].y));
        else printf("%lld\n",calc(stk[now-1],stk[now],i));
    }
    return 0;
}

posted @ 2019-03-14 21:57  Monster_Qi  阅读(359)  评论(0编辑  收藏  举报