NOIP模拟测试49·50「养花·折射·画作·施工·蔬菜·联盟」

一套题

养花

题解

分块\主席树

这里我用的是主席树

查询分段$1-(k-1)$找最大的,能向右找就向右找

        for(ll nowl=1,nowr=k-1;nowl<=maxx;nowl+=k,nowr+=k,nowr=min(nowr,maxx))
        {
            if(ans==mod-1) break;
            chose(rt[r],rt[l-1],nowl,nowr,1,maxx);
        }

复杂度分析,调和级数$√n*log(n)$

代码

#include
using namespace std;
#define ll int
#define rs(x) tr[x].son[1]
#define ls(x) tr[x].son[0]
#define A 10000000
struct tree{
    ll son[2],sz;
}tr[A];
ll tot,n,m,ans,maxx,mod;
ll a[500000],rt[500000];
void insert(ll &p,ll v,ll num,ll l,ll r){
    if(!p) p=++tot;
    if(l==r) {
        tr[p].sz=tr[v].sz+1;
        return ;
    }
    ll mid=(l+r)>>1;
    ll opt=num>mid,L,R;
    if(opt) L=mid+1,R=r;
    else L=l,R=mid;
    tr[p].son[opt^1]=tr[v].son[opt^1];
    insert(tr[p].son[opt],tr[v].son[opt],num,L,R);
    tr[p].sz=tr[ls(p)].sz+tr[rs(p)].sz;
}
void find(ll p,ll v,ll l,ll r){
    
    if(r%modreturn ;
//    printf("l=%d r=%d\n",l,r);
    if(l==r){
        ans=max(ans,l%mod);
        return ;
    }
    ll mid=(l+r)>>1;
    if(tr[rs(p)].sz-tr[rs(v)].sz) find(rs(p),rs(v),mid+1,r);
    else if(tr[ls(p)].sz-tr[ls(v)].sz)find(ls(p),ls(v),l,mid);
}
void chose(ll p,ll v,ll ql,ll qr,ll l,ll r){
    if(tr[p].sz-tr[v].sz==0)return ;
    if(l>=ql&&r<=qr){
        find(p,v,l,r);
        return ;
    }
    ll mid=(l+r)>>1;
    if(mid>=ql) chose(ls(p),ls(v),ql,qr,l,mid);
    if(mid1,r);
}
int main(){
    scanf("%d%d",&n,&m);
    for(ll i=1;i<=n;i++){
        scanf("%d",&a[i]);
        maxx=max(a[i],maxx);
    }
    for(ll i=1;i<=n;i++){
        insert(rt[i],rt[i-1],a[i],1,maxx);
    }
    for(ll i=1,l,r,k;i<=m;i++){
        scanf("%d%d%d",&l,&r,&k);
        ans=0;
        mod=k;
        for(ll nowl=1,nowr=k-1;nowl<=maxx;nowl+=k,nowr+=k,nowr=min(nowr,maxx))
        {
            if(ans==mod-1) break;
            chose(rt[r],rt[l-1],nowl,nowr,1,maxx);
        }
        printf("%d\n",ans);
    }
}
View Code

折射

题解

$n^2,dp$

$f[i][0/1]$表示向左延伸的光线,向右延伸的光线

按照$x$排序,然后考虑转移

枚举比当前点小的点$j$

若$j.y>i.y$

$f[j][1]+=f[i][0]$

$j.y

$f[i][0]+=f[j][1]$

你会发现这样转移会有不和法的

NOIP模拟测试49·50「养花·折射·画作·施工·蔬菜·联盟」_第1张图片

不要容斥,更改枚举顺序从大到小枚举

设最上点$x$,中间点为$y$,下面点为$z$

假设这次$y$贡献要加$1$,$x$加上$f[y]$如果是逆序就没加上当前贡献

代码

#include
using namespace std;
#define ll long long
#define A 6100
const ll mod=1e9+7;
ll f[A][2];
ll n,ans;
struct node{
    ll x,y;
    friend bool operator < (const node &a,const node &b){
        return a.x<b.x;
    }
}poi[A];
int main(){
    scanf("%lld",&n);
    for(ll i=1;i<=n;i++){
        scanf("%lld%lld",&poi[i].x,&poi[i].y);
    }
    sort(poi+1,poi+n+1);
    for(ll i=1;i<=n;i++){
        f[i][0]=f[i][1]=1;
        for(ll j=i-1;j>=1;j--){
            if(poi[j].y>poi[i].y)
                (f[j][1]+=f[i][0])%=mod;
            else (f[i][0]+=f[j][1])%=mod;
        }
    }
    for(ll i=1;i<=n;i++){
        (ans+=f[i][0]+f[i][1])%=mod;
    }
    ans-=n;
    printf("%lld\n",ans);
}
View Code

画作(同bzoj2638)

题解

轮流染色

将同色方块缩点建图
枚举每个点为根求bfs树
按深度从深至浅顺序染色
树的深度-(最深叶节点为白色?1:0)为以这个点为中心染色的最少操作次数

代码

#include
using namespace std;
#define ll long long
#define A 55
char c[A][A];
ll dis[A*10000],head[A*10000],nxt[A*10000],ver[A*10000],col[A*10000],id[A][A];
ll n,m,tot,ide,mx=0,ans=0x7fffffffff;
dequeq;
void add(ll x,ll y){
//    printf("x=%lld y=%lld\n",x,y);
    nxt[++tot]=head[x],head[x]=tot,ver[tot]=y;
}
ll ok(ll x,ll y){
    if(x>=1&&x<=n&&y>=1&&y<=m) return 1;
    return 0;
}
const ll nowx[5]={0,1,0,0,-1};
const ll nowy[5]={0,0,1,-1,0};
void dfs(ll x,ll y,ll now){
//    printf("x=%lld y=%lld now=%lld c[x][y]-'0'=%lld ide=%lld\n",x,y,now,1ll*(c[x][y]-'0'),ide);
    if(id[x][y]||c[x][y]-'0'!=now)    
        return ;
    id[x][y]=ide;
//    printf("n=%lld m=%lld\n",n,m);
    for(ll i=1;i<=4;i++){
        ll xnow=nowx[i]+x,ynow=nowy[i]+y;    
//        printf("x=%lld y=%lld xnow=%lld ynow=%lld\n",x,y,xnow,ynow);
        if(ok(xnow,ynow))
            dfs(xnow,ynow,now);
    }
}
int main(){
    scanf("%lld%lld",&n,&m);
    for(ll i=1;i<=n;i++){
        scanf("%s",c[i]+1);
    }
    
    for(ll i=1;i<=n;i++){
        for(ll j=1;j<=m;j++){
            if(!id[i][j]){
                col[++ide]=c[i][j]-'0';
                dfs(i,j,c[i][j]-'0');
            }
        }
    }
/*    for(ll i=1;i<=n;i++,puts("")){
        for(ll j=1;j<=m;j++){
            printf("%lld",id[i][j]);
        }
    }
*/    for(ll i=1;i<=n;i++)
        for(ll j=1;j)
            if(id[i][j]!=id[i][j+1]) 
                add(id[i][j],id[i][j+1]),add(id[i][j+1],id[i][j]);
    for(ll i=1;i)
        for(ll j=1;j<=m;j++)
            if(id[i][j]!=id[i+1][j])
                add(id[i][j],id[i+1][j]),add(id[i+1][j],id[i][j]);
    for(ll i=1;i<=ide;i++){
        for(ll j=1;j<=ide;j++) dis[j]=0x7ffffffff;
        q.clear();mx=0;
        q.push_back(i);
        dis[i]=0;
        while(!q.empty()){
            ll x=q.front();
            q.pop_front();
//            printf("x=%lld col=%lld\n",x,col[i]);
            if(dis[x]>mx) mx=dis[x];
            for(ll k=head[x];k;k=nxt[k]){
                ll y=ver[k];
//                printf("x=%lld y=%lld col=%lld dis[x]=%lld dis[y]=%lld mx=%lld\n",x,y,col[i],dis[x],dis[y],mx);
                if(dis[y]>dis[x]+1){
                    dis[y]=dis[x]+1;        
                    q.push_back(y);
                }
            }
        }
        if(col[i]==1&&!(mx&1)) mx++;
        if(col[i]==0&&(mx&1)) mx++;
        if(mxmx;
    }
    printf("%lld\n",ans);
}
View Code

蔬菜

裸二维莫队

施工

题解

模拟\dp

wwwwwwb的题解

 

可以想象到最优情况一定是将两端高于中间的一段平原填成一段平的坑,不然如果坑内存在高度差那么我们即使只将一部分抬升也肯定没有用处,并且如果中间的坑已经高于了两端,再向上升也肯定不优,然后就中间的坑可以很很小,也可以很长,对于这个模型我们首先想到n^2*h的DP

设当前表示的f[i]表示当前到了i节点并且i节点高度不变时的花费,那么能转移到他的其实只有高于i节点高度的点,那么我们

其实可以考虑用单调栈维护

然后对于h的最优高度化简后是一个二次函数的形式,然后直接求解就好,然后求二次函数时要注意解的范围,也要注意解是否为整数,然后打了很久..........


我的理解填坑是肯定的,不一定要把坑填满,但不会漏情况,你考虑之前点时包含了没把坑填满的情况,

然后单调栈维护,维护单调递减的情况,我们选到之前点继承了之前最短的情况,不一定要填满

NOIP模拟测试49·50「养花·折射·画作·施工·蔬菜·联盟」_第2张图片

 

填中间

NOIP模拟测试49·50「养花·折射·画作·施工·蔬菜·联盟」_第3张图片

 

 这样情况是考虑了的我们并没有漏掉这种情况,我们填当前先覆盖了一遍

另一个点是

        while(top>1&&a[sta[top]]<=a[i]){
            if(top>=2){
                f[i]=min(f[i],f[sta[top-1]]+gedit(sta[top-1],i,a[sta[top]]));
            }
            top--;
        }
        sta[++top]=i;

为什么栈顶之前一个位置,这是决策点,sta[top]是这之间最大值,至少要填到a[sta[top]]

 

联盟

题解

危险最大值就是直径,断直径上的点才会使答案变优,

连边的话,我们要连边且让新的直径代价最小,连两个子树直径的中点代价才会最小,(感性理解,如果你不在中点取,直径可以是链接这条直径较长端)

因此我们可以轻易得到一个$n^2$算法,枚举断边,然后求子树内直径

如何优化时间,发现子树内直径就是子树内最长链+次长链,

于是我们就可以O1查出来了

思维并不算难,代码也不算难打

代码

#include
#define A 600005
#define ll long long
ll f[2][A],head[A],nxt[A],ver[A],dis[A],edgst[A],edged[A],dl[A],bl[A],zj[A],tmpans[A],iszj[A];
ll st,ed,n,m,cnt=0,ans=0x7fffffffffff,tot=1,ban=-1,ban2=-1;
using namespace std;
void add(ll x,ll y){
    nxt[++tot]=head[x],head[x]=tot,ver[tot]=y;
}
ll dfs1(ll x,ll pre,ll opt){
    ll mx=0;
    for(ll i=head[x];i;i=nxt[i]){
        ll y=ver[i];
        if(y==pre) continue ;
        ll d=dfs1(y,x,opt);
        f[opt][x]=max(f[opt][x],f[opt][y]);
        f[opt][x]=max(f[opt][x],d+mx);
        mx=max(mx,d);
    }
//    子树内直径特殊求法
    return mx+1;
}
void dfs2(ll x,ll pre){
    for(ll i=head[x];i;i=nxt[i]){
        ll y=ver[i];
        if(y==pre||i==ban||(i^1)==ban) continue ;
        dis[y]=dis[x]+1;
        dfs2(y,x);
        bl[y]=i;
        edgst[i>>1]=x;
        edged[i>>1]=y;
    }
}
void getfa(){
    ll edd=ed;
    while(edd!=st){    
//        printf("edd=%lld bl[%lld]=%lld st=%lld ed=%lld\n",edd,edd,bl[edd],st,ed);
        iszj[bl[edd]>>1]=1;
        zj[++cnt]=edd;
        edd=ver[bl[edd]^1];
    }
    zj[++cnt]=st;
}
void want_print(ll x){
    ban=x<<1;
    cnt=0;
    ll tobe1=edgst[x],tobe2=edged[x];
    st=1,ed=1;
    memset(dis,0,sizeof(dis));
    dfs2(tobe1,0);
    for(ll i=1;i<=n;i++)
        if(dis[i]>dis[st]) st=i;
    dis[st]=0;dfs2(st,0);
    for(ll i=1;i<=n;i++)
        if(dis[i]>dis[ed]) ed=i;
    getfa();
//    for(ll i=1;i<=cnt;i++){
//        printf("zj%lld\n",zj[i]);
//    }
    printf("%lld ",zj[(cnt+1)>>1]);
    cnt=0;
    st=1,ed=1;
    memset(dis,0,sizeof(dis));
    dfs2(tobe2,0);
    for(ll i=1;i<=n;i++)
        if(dis[i]>dis[st]) st=i;
    dis[st]=0;dfs2(st,0);
    for(ll i=1;i<=n;i++)
        if(dis[i]>dis[ed]) ed=i;
    getfa();
    printf("%lld ",zj[(cnt+1)>>1]);
}
void sub_task(){
    st=1,ed=1;
    dis[st]=0;dfs2(st,0);//找到直径起始点
    for(ll i=1;i<=n;i++)
        if(dis[i]>dis[st]) st=i;
    dis[st]=0;dfs2(st,0);//找到直径终止点
    for(ll i=1;i<=n;i++)
        if(dis[i]>dis[ed]) ed=i;
    dfs1(st,0,0);//处理子树最大直径
    dfs1(ed,0,1);//处理子树最大直径
    getfa();//处理直径上的边
    ll len=dis[ed]-dis[st];
    for(ll i=1;i){
        tmpans[i]=0;
        if(iszj[i]) tmpans[i]=max(max(f[0][edged[i]],f[1][edgst[i]]),(f[0][edged[i]]+1)/2+(f[1][edgst[i]]+1)/2+1);
        else tmpans[i]=max(len,min((f[1][edged[i]]+1)/2,(f[1][edgst[i]]+1)/2)+1+(len+1)/2);
        ans=min(ans,tmpans[i]);
//        printf("tmpans[%lld]=%lld len=%lld iszj[%lld]=%lld f[0][%lld]=%lld f[1][%lld]=%lld\n",i,ans,len,i,iszj[i],edged[i],f[0][edged[i]],edgst[i],f[1][edgst[i]]);
    }
    printf("%lld\n",ans);
    for(ll i=1;i){
        if(tmpans[i]==ans) dl[++dl[0]]=i;
    }
    printf("%lld ",dl[0]);
    for(ll i=1;i<=dl[0];i++)
        printf("%lld ",dl[i]);
    printf("\n");
    for(ll i=1;i){
        if(tmpans[i]==ans){
            printf("%lld %lld ",ver[i<<1|1],ver[i<<1]);
            want_print(i);
            break;
        }
    }
}
int main(){
    scanf("%lld",&n);
    for(ll i=1,a,b;i){
        scanf("%lld%lld",&a,&b);
        add(a,b);add(b,a);
    }
    sub_task();
}
View Code

 

你可能感兴趣的:(NOIP模拟测试49·50「养花·折射·画作·施工·蔬菜·联盟」)