NOI 2004 题解

郁闷的出纳员

(传送门)

题意

多个操作,可加入员工,可修改员工工资(加减),当工资低于K需离开,并支持查询第K大的工资

分析

看到多操作一般都是数据结构题,阀盖splay裸题,不赘述了

代码

#include <bits/stdc++.h>
using namespace std;

const int MAXN=2e5+100;
int lim;
struct SplayTree 
{  
    int sz[MAXN];  
    int ch[MAXN][2];  
    int pre[MAXN];  
    int rt,top;  
    inline void up(int x)
    {  
        sz[x]=cnt[x]+sz[ ch[x][0] ]+sz[ ch[x][1] ];  
    }
    inline void Rotate(int x,int f)
    {  
        int y=pre[x];  
        ch[y][!f]=ch[x][f];  
        pre[ ch[x][f] ]=y;  
        pre[x]=pre[y];  
        if(pre[x]) ch[ pre[y] ][ ch[pre[y]][1]==y ] =x;  
        ch[x][f]=y;  
        pre[y]=x;  
        up(y);  
    }
    inline void Splay(int x,int goal)
    {//将x旋转到goal的下面  
        while(pre[x] != goal)
        {  
            if(pre[pre[x]]==goal) Rotate(x ,ch[pre[x]][0]==x);  
            else   
            {  
                int y=pre[x],z=pre[y];  
                int f=(ch[z][0]==y);  
                if(ch[y][f]==x) Rotate(x,!f),Rotate(x,f);  
                else Rotate(y,f),Rotate(x,f);  
            }
        }
        up(x);  
        if(goal==0) rt=x;  
    }
    inline void RTO(int k,int goal)
    {//将第k位数旋转到goal的下面  
        int x=rt;  
        while(sz[ ch[x][0] ] != k-1) 
        {  
            if(k < sz[ ch[x][0] ]+1) x=ch[x][0];  
            else 
            {  
                k-=(sz[ ch[x][0] ]+1);  
                x=ch[x][1];  
            }
        }
        Splay(x,goal);  
    }
    inline void Newnode(int &x,int c)
    {  
        x=++top;  
        ch[x][0]=ch[x][1]=pre[x]=0;  
        sz[x]=1; cnt[x]=1;  
        val[x]=c;  
    }
    inline void init()
    {  
        sum=ch[0][0]=ch[0][1]=pre[0]=sz[0]=0;  
        rt=top=0; cnt[0]=0;  
    }
    inline void Insert(int &x,int key,int f)
    {  
        if(!x) 
        {  
            Newnode(x,key);  
            pre[x]=f;  
            Splay(x,0);  
            return ;  
        }
        if(key==val[x])
        {  
            cnt[x]++;  
            sz[x]++;  
            Splay(x,0);  
            return ;  
        }else 
            if(key<val[x]) 
            {  
                Insert(ch[x][0],key,x);  
            } 
            else 
            {  
                Insert(ch[x][1],key,x);  
            }
        up(x);  
    }
    void del(int &x,int f)
    {  
        if(!x) return ;  
        if(val[x]>=lim)
        {  
            del(ch[x][0],x);  
        } else 
        {  
            sum+=sz[ch[x][0]]+cnt[x];  
            x=ch[x][1];   
            pre[x]=f;  
            if(f==0) rt=x;  
            del(x,f);  
        }
        if(x)  up(x);  
    }
    inline void update()
    {  
        del(rt,0);  
    }
    inline int find_kth(int x,int k)
    {  
        if(k<sz[ch[x][0]]+1) 
        {  
            return find_kth(ch[x][0],k);  
        }else if(k > sz[ ch[x][0] ]+cnt[x] )   
            return find_kth(ch[x][1],k-sz[ch[x][0]]-cnt[x]);  
        else
        {   
            Splay(x,0);  
            return val[x];  
        }
    }
    int cnt[MAXN];  
    int val[MAXN];  
    int sum;  
}spt;

int main()
{
    int n,m;
    char op[5];
    scanf("%d%d",&n,&m);
    int w=0;
    spt.init();
    while(n--)
    {
        int k;
        scanf("%s%d",op,&k);
        if(op[0]=='I')
        {
            if(k<m)
            {
                continue;
            }
            spt.Insert(spt.rt,k-w,0);
        }
        else if(op[0]=='A')
        {
            w+=k;
        }
        else if(op[0]=='S')
        {
            w-=k;
            lim=m-w;
            spt.update();
        }
        else
        {
            int sz=spt.sz[spt.rt];
            if(k>sz) printf("-1\n");
            else  printf("%d\n",spt.find_kth(spt.rt,sz-k+1)+w);
        }
    }
    printf("%d\n",spt.sum);
    return 0;
}

曼哈顿

(传送门)

题意

有m*n的道路网,每条路都是单行,可对道路进行改造(工作量需要尽可能的小),使得输入限制的到路口之间走到的距离是而这的曼哈顿距离里

分析

这是一道不太明显的动态规划题。

 对于两条横向道路A,B,考虑四种不同的方向,然后纵向道路需要满足怎样的方向才可以满足题目要求,预处理出来。

这样,问题就转换成:寻找一种最小花费的纵向道路方案,使得其满足一系列类似这样的要求:在某个区间内至少存在一种某方向的纵向街道。然后就一目了然了。

代码

#include <bits/stdc++.h>
using namespace std;

const int maxn=100+10,maxl=200+10;
const int INF=0x3f3f3f3f;
int n,m,K,r[3],rr[3][maxn],ll[3][maxn],costm[maxn][3],costn[maxn][3];
int sm[maxn],sn[maxn],vm[maxn],vn[maxn],f[maxn][maxl][maxl];
char s[maxn];
struct req { int x1,x2,y1,y2;} a[maxn];
struct inter { int l,r;} in[3][maxl];
   
inline bool cmp(const inter &a,const inter &b) 
{ return a.l<b.l || (a.l==b.l && a.r<b.r);}
   
void maintain()
{
    sort(in[0]+1,in[0]+r[0]+1,cmp);
    sort(in[1]+1,in[1]+r[1]+1,cmp);
    for(int q=0;q<2;++q)
    {
        for(int i=1;i<=n;++i)
        {
            int j=1;ll[q][i]=1;
            for(;j<=r[q];++j)
            if(in[q][j].l>i) break;
            else if(in[q][j].r<i) ll[q][i]=j+1;
            rr[q][i]=j;
        }
    }
}
   
void add(int p,int L,int R)
{
    if(L>R) swap(L,R);
    for(int i=1;i<=r[p];++i)
        if(L<=in[p][i].l && in[p][i].r<=R) return;
    in[p][++r[p]].l=L,in[p][r[p]].r=R;
}
   
bool prepare(int s)
{
    memset(rr,0 ,sizeof rr);
    memset(ll,0,sizeof ll);
    r[0]=0,r[1]=0;
    for(int i=1;i<=K;++i)
    {
        int x1=a[i].x1,y1=a[i].y1,x2=a[i].x2,y2=a[i].y2;
        int p=(x1<x2),q=(y1<y2);
        if(x1!=x2 && y1!=y2)
        {
            if(((s >> (x1-1)) & 1)==q && ((s >> (x2-1)) & 1)==q) add(p,y1,y2);
            else if(((s >> (x1-1)) & 1)==q) add(p,y2,y2);
            else if(((s >> (x2-1)) & 1)==q) add(p,y1,y1);
            else
            {
                int t=0,fr=min(x1,x2)+1,en=max(x1,x2);
                for(int j=fr;j<en;++j)
                    if(((s >> (j-1)) & 1)==q) t=1;
                if(!t) return 0;
                add(p,y1,y1);add(p,y2,y2);
            }
        }
        else if(x1==x2 && y1!=y2 && ((s >> (x1-1)) & 1) != q) return 0;
        else if(x1!=x2 && y1==y2) add(p,y1,y2);
    }
    maintain();
    return 1;
}
   
int dp()
{
    memset(f,0x7,sizeof f);
    f[1][1][1]=0;
    for(int i=1;i<=n;++i)
    for(int j=ll[0][i];j<=rr[0][i];++j)
    for(int k=ll[1][i];k<=rr[1][i];++k)
    if(f[i][j][k]<INF)
    {
        if(f[i+1][rr[0][i]][k]>f[i][j][k]+costn[i][0])
            f[i+1][rr[0][i]][k]=f[i][j][k]+costn[i][0];
        if(f[i+1][j][rr[1][i]]>f[i][j][k]+costn[i][1])
            f[i+1][j][rr[1][i]]=f[i][j][k]+costn[i][1];
    }
    return f[n+1][r[0]+1][r[1]+1];
}
   
int main()
{
    scanf("%d%d",&m,&n);
    scanf("%s",s);
    for(int i=1;i<=m;++i) sm[i]=(s[i-1]=='E');
    scanf("%s",s);
    for(int i=1;i<=n;++i) sn[i]=(s[i-1]=='S');
    for(int i=1;i<=m;++i) 
    {
        scanf("%d",vm+i);
        costm[i][sm[i]]=0,costm[i][1-sm[i]]=vm[i];
    }
    for(int i=1;i<=n;++i) 
    {
        scanf("%d",vn+i);
        costn[i][sn[i]]=0,costn[i][1-sn[i]]=vn[i];
    }
    scanf("%d",&K);
    for(int i=1;i<=K;++i) 
        scanf("%d%d%d%d",&a[i].x1,&a[i].y1,&a[i].x2,&a[i].y2);
        
    int tot=(1<<m),ans=INF;
    for(int i=0;i<tot;++i)
    {
        int t=0;
        for(int j=1;j<=m;++j) t += costm[j][(i>>(j-1))&1];
        if(prepare(i))
        {
            t += dp();
            ans=min(ans,t);
        }
    }
    if(ans<INF)
    {
        printf("possible\n");
        printf("%d\n",ans);
    } else printf("impossible\n");
    return 0;
}


降雨量

(传送门)

题意

一些在不同高度的伞(线段)可以左右移动,给定初始方向和速度。求开始下雨到T时间有多少雨水落在了地面上

分析

可以用Simpson自适应公式来做,但显然那样会比较麻烦。

我们用分段求面积的方法来做,这利用了所有图形都是平行四边形的特殊性。

将坐标轴反过来,tx,位置为y轴,然后找到所有线段的交点(图自己画一下吧)

由图可知,所有的交点将图分了好多部分,每一部分的面积是连续变化的,用公式area(a,b)=(s(b)+s(a))*(b-a)/2计算,s(a)指直线x=a在图形上截得的总面积。将所有区间面积加起来。

代码

#include <bits/stdc++.h>
using namespace std;

const int maxn=301,maxm=5010;
const double eps=1e-8;
int n,m,w,T,V,ind;
double a[maxm];

struct line
{
    double x,y,len,k,L,R;
} L[maxm];
struct inter
{
    double l,r;
} c[maxn];
bool cmp(const inter &a,const inter &b) 
{
    return a.l<b.l || (a.l == b.l && a.r<b.r);
}

double f(double x)
{
    double s=0;int p=0;
    for(int i=1;i<=m;i++)
    if(L[i].L<=x && x<=L[i].R)
    {
        c[++p].l=(x-L[i].x)*L[i].k+L[i].y;
        c[p].r=c[p].l+L[i].len;
    }
    if(!p) return 0;
    sort(c+1,c+p+1,cmp);
    double l=c[1].l,r=c[1].r;
    for(int i=2;i<=p;i++)
        if(c[i].l>r)
        {
            s+=r-l;
            l=c[i].l;
            r=c[i].r;
        }
        else r=max(c[i].r,r);
    
    s+=(r-l);
    return s;
}

double area()
{
    double s=0;
    for(int i=2;i<=ind;i++)
        s += (f(a[i])+f(a[i-1]))*(a[i]-a[i-1])/2;
    return s;
}

void check(double x,double y,double len,double v,double t)
{
    L[++m].L=x,L[m].R=x+t,L[m].k=v;
    L[m].x=x,L[m].y=y,L[m].len=len;
    double l=y+t*v,r=y+len+t*v;
    if(r>w+eps)
    {
        L[m].R=x+(w-len-y)/v;
        check(L[m].R,w-len,len,-v,t-(w-len-y)/v);
    } else
    if(l<-eps)
    {
        L[m].R=x-y/v;
        check(L[m].R,0,len,-v,t+y/v);
    } else a[++ind]=x+t;
    a[++ind]=x;
}

bool cross(int i,int j)
{
    return L[i].R>=L[j].L && L[i].L<=L[j].R && fabs(L[i].k-L[j].k)>eps;
}

void get_p(int i,int j)
{
    double x1=L[i].x,x2=L[j].x,y1=L[i].y,y2=L[j].y;
    double k1=L[i].k,k2=L[j].k,L1=L[i].L,L2=L[j].L;
    double R1=L[i].R,R2=L[j].R,len1=L[i].len,len2=L[j].len;
    double x=(y2-y1+x1*k1-x2*k2)/(k1-k2);
    if(L1<=x && x<=R1 && L2<=x && x<=R2)
        a[++ind]=x;
    x=(y2+len2-y1+x1*k1-x2*k2)/(k1-k2);
    if(L1<=x && x<=R1 && L2<=x && x<=R2)
        a[++ind]=x;
    x=(y2-len1-y1+x1*k1-x2*k2)/(k1-k2);
    if(L1<=x && x<=R1 && L2<=x && x<=R2)
        a[++ind]=x;
    x=(y2+len2-len1-y1+x1*k1-x2*k2)/(k1-k2);
    if(L1<=x && x<=R1 && L2<=x && x<=R2)
        a[++ind]=x;
}

int main()
{
    scanf("%d%d%d%d",&n,&w,&T,&V);
    for(int i=1;i<=n;i++)
    {
        double x,l,v;
        scanf("%lf%lf%lf",&x,&l,&v);
        if(x || l!=w) check(0,x,l,v,(double)T);
        else
        {
            printf("0.00");
            return 0;
        };
    }
    for(int i=1;i<=m;i++)
        for(int j=1;j<=m;j++)
            if(i!=j && cross(i,j))
                get_p(i,j);
    sort(a+1,a+ind+1);
    printf("%.2lf\n",(T*w-area())*V);
    return 0;
}


小H的小屋

(传送门)

题意

小屋东西长为100,东西墙均平行于y轴,南墙分别是斜率为k1和k2的直线(k1和k2为正实数)。南北墙的墙角处有很多块草坪,每块草坪都是一个矩形,矩形的每条边都平行于坐标轴。相邻两块草坪的接触点恰好在墙上,接触点的横坐标必须是1到99的整数。在北墙角要有m块草坪,在南墙角要有n块草坪,并约定m≤n。如果记北墙和南墙的分点集合分别为X1,X2,则应满足X1是X2的约数,即北墙的任何一个分点一定是南墙的分点。同时草坪的占地总面积最小

分析

首先想到贪心,但是只会有40分,这是一道动态规划题,比较难想

dp[i][j][k]表示长度为k,北边有i个草坪,南边有j个草坪时的最小面积
因为北边的草坪数少于南边,所以按北边来递推,这样也可不用考虑状态是否合法
预处理s[i][j]表示距离为i时,南边有j块草坪的最小面积
dp[i][j][k]=min(dp[i][j][k],dp[i-1][j'][k']+kb*(k-k')^2+s[k-k'][j-j'])

代码

#include <bits/stdc++.h>
using namespace std;

const int MAXN=100+5;
const double INF=0x3f3f3f3f;

double kb,kn,g[MAXN][MAXN],dp[MAXN][MAXN][MAXN];
int m,n;

int main()
{
    scanf("%lf%lf%d%d",&kb,&kn,&m,&n);

    for(int i=0;i<MAXN;i++)
        for(int j=0;j<MAXN;j++)
        { 
            for(int k=0;k<MAXN;k++)
                dp[i][j][k]=INF;
            g[i][j]=INF;
        }

    g[0][0] =0;
    for(int i=1;i<=100;i++)
        for(int j=1;j<=i;j++)
        {
            for(int k=j-1;k<i;k++)
                g[i][j]=min(g[i][j],g[k][j-1]+(i-k)*(i-k)*kn);
        }

    dp[0][0][0]=0;
    for(int k=1;k<=100;k++)
        for(int i=1;i<=m;i++)
            for(int j=1;j<=n;j++)
                for(int kk=i-1;kk<k;kk++)
                    for(int jj=i-1;jj<j;jj++)
                        dp[i][j][k]=min(dp[i][j][k],dp[i-1][jj][kk]+(k-kk)*(k-kk)*kb+g[k-kk][j-jj]);

    printf("%.1lf",dp[m][n][100]);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值