2011

2011D1T2客栈
Task
N个点,两个属性c,v表示颜色和花费,求区间[ l, r ]满足l,r颜色相同,且这个区间最小值<=p的个数

N<=2e5,c<=50,p<=100

Solution
如果枚举两个端点,复杂度是O(n^2),用前缀和询问这个区间是否有<=p的数,来判断这个区间是否可行。
区间问题常见的优化方法:枚举右端点,找左端点。
因为颜色的个数很小,因此可以按颜色分类,放入vector里。找到每个点前面最近的<=p的点,那么位置在p之前的点都可以成为左端点。用STL里的lower_bound函数处理很方便。

其实,这题还可以排列组合来做,复杂度O(nk)。个数=总区间数-不可行的区间数。
正难则反,只要求出不可能的区间数,v<=p的点会把这个区间分成一段一段的,那么单独一段里的就是不含v<=p的点,这一段内部的区间都是不可行的的区间

const int M=53;
vector<int>s[M];
inline int query(int c,int x){return upper_bound(s[c].begin(),s[c].end(),x)-s[c].begin();}
int main(){
    int i,j,k,c,val,pos=-1,tot,p,n,res;
    ll ans=0;
    rd(n);rd(tot);rd(p);
    rep(i,1,n){
        rd(c);rd(val);
        if(val<=p)pos=i;
        s[c].pb(i);
        if(pos==-1)continue;
        if(pos==i)ans+=query(c,pos-1);
        else ans+=query(c,pos);
    }   
    cout<<ans<<endl;
    return 0;
}

2011D1T3 游戏
Task
如果存在,在7*5的界面,输出k操作后,方格都被消除的字典序最小的方案。
操作规则:
① 每次选择一个方格与它相邻的左或右方格交换,当前不能是空方格,左右可以是空方格
② 操作以后,方格会先落下来,再消除
③ 任意行列有超过3个同颜色的方格会被消除

颜色少于10种,操作数k<=5,字典序的关键字次序是 x,y,左比右小
对于30%数据,初始方格都在棋盘最下面一行
Solution
由题意和k<=5确定这是一道暴搜题。类似做过的题目有popstar 消除星星
剪枝:
① 交换的两个颜色相同,则不交换
② 当前存在某个颜色少于3个,这个状态是一定不可行的。
暴搜题只要确定好思路,定下框架,就一定能成功。
把消除,掉落,检查颜色个数都用函数来处理。
下面是我在搜索过程中犯的错误
① 题中的x表示列,y表示行,因为用的不习惯,把它转换了,但是最后忘记换回去了。
② 掉落的Fall函数中,一列列处理去除中间空的部分,但是如果遇到空的,不能break,它上面可能还有实方格。

/*
    x∈[1,n],y∈[1,m]
    相反输出 
    能在函数里改变结构体的值: 全局变量 传入数组 &node 
*/
int ry[]={1,-1};
int n=7,m=5,tot,maxcol,yzq;
bool vis[8][6],flag;
int ans[7][5];//记录答案 
bool mark[5];
struct node{
    int a[8][6];    
}A;
inline void input(){
    int i,j,k,num=0;
    rd(tot);
    rep(j,1,m){
        num=0;
        while(1){
            rd(k);
            if(k==0)break;
            A.a[++num][j]=k;
        }
    }
}
inline bool Empty(int a[8][6]){//检查第一层 
    int i,j;
    rep(j,1,m)if(a[1][j]>0)return 0;
    return 1;
}
inline bool Nosolution(int a[8][6]){//剪枝:存在某一个颜色个数<=2 则一定不可能  
    int i,j,cnt[11];
    memset(cnt,0,sizeof(cnt));
    rep(j,1,m)
        rep(i,1,n){
            if(a[i][j]==0)break;
            cnt[a[i][j]]++;
        }
    rep(i,1,maxcol)if(cnt[i]>0&&cnt[i]<=2)return 1;
    return 0;
}
inline bool Wipe(int a[8][6]){//消除行列超过3个连续的  
    bool f=0;//标记存在消除 
    int i,j,k,p;
    memset(vis,0,sizeof(vis));//标记这个点是否被消除 
    rep(i,1,n)//处理行连续 
        for(j=1;j<=m;j=k){
            k=j;
            while(k<=m&&a[i][k]==a[i][j])k++;//注意下标越界 
            if(a[i][j]==0)continue;
            if(k-j>=3){//存在至少3个连续的 
                rep(p,j,k-1)vis[i][p]=1;
                f=1;
            }
        }
    rep(j,1,m)//处理列连续 
        for(i=1;i<=n;i=k){
            k=i;
            while(k<=n&&a[k][j]==a[i][j])k++;//注意下标越界 
            if(a[i][j]==0)continue;
            if(k-i>=3){
                f=1;
                rep(p,i,k-1)vis[p][j]=1; 
            }
        }
    return f;
}
inline bool Fall(int a[8][6]){
    int i,j,num;
    int b[8][6];
    memset(b,0,sizeof(b));//初始化数组 
    rep(j,1,m){//一列列处理,除去中间空的部分 如果遇到空的,上面可能还有方格 
        num=0;
        rep(i,1,n){
            if(a[i][j]==0||vis[i][j]==1)continue;
            b[++num][j]=a[i][j];
        }
    }
    rep(i,1,n)rep(j,1,m)a[i][j]=b[i][j];//赋值回去 
}
inline void print(node A,int c){
    int i,j;
    per(i,n,1){
        rep(j,1,m)
            printf("%d ",A.a[i][j]);
        puts("");
    }
    putchar('\n');
}
inline void dfs(node A,int c){//剪枝:避免重复
    if(c==0){//递归终点 已经用完了所有的步数 
        if(Empty(A.a))flag=1;
        return;
    }
    if(Nosolution(A.a))return;
    int i,j,k,nj;
    rep(j,1,m){//交换相邻两个 x,y次序和题目相反 
        if(flag)break;
        rep(i,1,n){
            if(flag)break;
            if(!A.a[i][j])break;//空方块不能交换 
            ans[c][0]=i,ans[c][1]=j;
            rep(k,0,1){//确定方向
                if(flag)break;
                nj=j+ry[k];
                if(nj>m||nj<1)continue;//数组越界 
                if(A.a[i][j]==A.a[i][nj])continue;//剪枝:相邻两个颜色相同,不交换
                if(A.a[i][nj]>0&&k==1)continue;//如果左边不是空方格,那么可以由左边的向右交换 
                ans[c][2]=ry[k];
                node B=A;
                swap(B.a[i][j],B.a[i][nj]);
                Fall(B.a);//先掉下来一个 
                while(Wipe(B.a))Fall(B.a);//当可以消除的时候,可能会一直落下来 
                dfs(B,c-1); 
            }
        }
    }   
}
inline void solve(){
    dfs(A,tot);
    int i;
    if(!flag)puts("-1");
    else per(i,tot,1)printf("%d %d %d\n",ans[i][1]-1,ans[i][0]-1,ans[i][2]);
}
int main(){
    input();
    solve();
    return 0;
    /*
    0 0 0 0 0 
    0 0 0 0 0 
    0 0 0 0 0 
    0 0 0 3 4 
    0 1 2 4 3 
    0 2 3 1 4 
    0 1 2 3 4 
    */
}

2011D2T2
监质员
Task
N个点,两个属性w,v表示重量和价值。标准值为S。
流程是
这里写图片描述
请确定一个参数W,使检验总和与标准值之差的绝对值最小,输出最小绝对值

Solution
发现W有单调性。
如果W越小,每个区间符合条件个数越多,价值和越大。
因此可以二分处理。
如果当前总和比S小,W变小,反之变大,一直逼近S,得到最小的绝对值。

const int M=2e5+5;
int w[M],v[M],L[M],R[M],cnt[M];
int n,m;
ll S,sum[M],ans=1e18;
inline void input(){
    rd(n);rd(m);rd(S);
    rep(i,1,n)rd(w[i]),rd(v[i]);
    rep(i,1,m)rd(L[i]),rd(R[i]);
}
inline bool check(int x){
    int i,j,k,a=0;
    ll b=0,res=0;
    rep(i,1,n){
        cnt[i]=cnt[i-1];
        sum[i]=sum[i-1];
        if(w[i]>=x){
            cnt[i]++;
            sum[i]+=v[i];   
        }
    }
    rep(i,1,m)res+=1ll*(cnt[R[i]]-cnt[L[i]-1])*(sum[R[i]]-sum[L[i]-1]);
    MIN(ans,abs(res-S));
    return res>=S; 
}
inline void solve(){
    int i,j,k,l,r,mid,res;
    l=0,r=1e7;
    while(l<=r){
        mid=l+r>>1;
        if(check(mid)){l=mid+1;res=mid;}
        else r=mid-1;
    }
    i=check(res+1);
    cout<<ans<<endl;
}
int main(){
    input();
    solve();
    return 0;
}

2011D2T3
观光
Task
n个点在一条直线上,任意两点距离为di。m个人,在Ti时刻到Ai去Bi( Ai小于Bi )。公交必须等每个人i点等待的人都到达后,才能从i出发。旅行时间为结束时刻-初始时刻。K个加速器,可以让相邻两点间的d-1,但是不能减为负数。
求最小的总旅行时间。

N<=1e3,m<=1e4,k<=1e5,k<=100,T<=1e5

Solution
定义st[i]为从这个点出发的时间,是已经确定的。
arr[i]为最早到达i点的时间。
如果修改了区间[ i, i+1 ]的d,那么影响的是之后一段连续区间的arr值,直到有一个st值比较大,之后的arr值都不会改变了。
因此我们可以计算,修改每一小段区间的d值,可以影响到arr的一段区间[ l , r ],那么此时在[ l ,r ]区间下车的人结束的时间都会少1.
贪心地每次去选影响区间内下车人数最多的点,根据相邻交换法,可以证明是正确的。

const int M=1005;
int st[M],arr[M],far[M],sum[M],d[M];
/*
    sum(i) 在[1,i]下车的人数
    far(i) 第一个不能更新arrive的点 
    st(i) i点最早的开始时刻 
    arr(i)=max( st(i-1),arr(i-1) )+d(i-1) 到达i的时间 
    d(i)-1时 更新的范围是( i+1,far(i) ) 
*/
int n,m,tot,ans;
inline void input(){
    int i,j,k,t,a,b;
    rd(n);rd(m);rd(tot);
    rep(i,1,n-1)rd(d[i]);
    rep(i,1,m){
        rd(t);rd(a);rd(b);
        MAX(st[a],t);
        sum[b]++;
        ans-=t;
    }
}
inline void init(){//不用加压器的情况 
    int i,j,k;
    rep(i,2,n){
        arr[i]=max(st[i-1],arr[i-1])+d[i-1];
        ans+=sum[i]*arr[i];//以当前点为结束时刻的总时间 
    }
    rep(i,1,n+1)sum[i]+=sum[i-1];
}
inline int cal(){//计算最远到达的点  影响最优的点 
    int i,j,x=0;
    far[n]=n+1; 
    per(i,n-1,1){
        if(arr[i+1]>st[i+1])far[i]=far[i+1];//下一个点可更新
        else far[i]=i+1;//不能更新 
        if(d[i]>0&&sum[far[i]]-sum[i]>sum[far[x]]-sum[x])x=i;//影响最大的点 
    }
    d[x]--;
    rep(i,x+1,far[x])arr[i]--;
    return x;
}
inline void solve(){
    int x;
    while(tot--){//贪心 每次找最优的 
        x=cal(); 
        ans-=sum[far[x]]-sum[x];
    }
    printf("%d\n",ans);
}
int main(){
    input();
    init();
    solve();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值