2011 Noip Day1 & Day2

Noip 2011
 



Day 1   >>>




T1   >>


水题一道 。

我们只需要 for 一遍 , 由于地毯是从下往上铺的 , 我们只需要记录该位置最上面的地毯的编号 , 每一次在当前地毯范围内就更新编号 , 最后的答案就是最后更新的编号。


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

struct node{
    int xmin,ymin,xmax,ymax;
}a[10005];

int n,lenx,leny,xreq,yreq,ans=-1;

inline void read(int &x) {
    int f=1;x=0;char c=getchar();
    while(c>'9'||c<'0') {if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9') x=10*x+c-48,c=getchar();
    x=x*f;
}

int main(){
    
    //freopen("carpet.in","r",stdin);
    //freopen("carpet.out","w",stdout);
    
    read(n);
    for(int i=1;i<=n;i++){
        read(a[i].xmin);read(a[i].ymin);read(lenx);read(leny);
        a[i].xmax=a[i].xmin+lenx;
        a[i].ymax=a[i].ymin+leny;
    }
    read(xreq);read(yreq);
    for(int i=1;i<=n;i++)
        if((xreq>=a[i].xmin && xreq<=a[i].xmax) && (yreq>=a[i].ymin && yreq<=a[i].ymax))
            ans=i;
    printf("%d",ans);
    return 0;
}



T2   >>

QwQ , 最开始想写线段树的 :


对于每两个相同颜色的旅馆 X 和 Y , 咱们可以维护一个线段树 , 或者 ST 表 , 用 logN 的时间内找出 X 到 Y 之间的最小值小于等于 P 即可。但是必要的枚举 N 个起点 , K 种颜色 ,负责度已经达到了 10^7 了 ,在乘上 logn ,一定超时 , 我们必须要考虑优化:

由于从某一个 i 旅馆开始向后枚举 , 或许会找到一段区间 [ i , j ] 满足题目要求 , 那么这之后的所有 [ i , j + 1 ~ N ],一定满足要求;这样就可以做到不比枚举所有区间的优化。这是评测之后想出来的优化 , 很后悔 , 自己之前的优化不是很好 , 没有优化太多;一开始收上去 , 然而在 Cena 上 T 掉了 , 最后发现可以优化 , 只是我的优化办法太 low 了 , 完全是劣化 !

看看优化以后的代码:

#include<bits/stdc++.h>

#define RG register

const int N=200000+5,K=50+5;

using namespace std;

int color[K][N],vmin[N<<4],cost[N];
int k,n,p,cc,tmp,tmp1,tmp2;
long long ans;
bool ok;

int minx(int a,int b){
    return a < b ? a : b ;
}

void update(int o){
    vmin[o]=minx(vmin[o<<1],vmin[o<<1|1]);
}

void build(int o,int l,int r){
    if(l==r){
        vmin[o]=cost[l];
        return ;
    }
    int mid=(l+r)>>1;
    build(o<<1,l,mid);
    build(o<<1|1,mid+1,r);
    update(o);
}

int query(int o,int l,int r,const int L,const int R){
    if(L<=l && r<=R)
        return vmin[o];
    int mid=(l+r)>>1,cnt=0x7fffffff;
    if(L<=mid) cnt=minx(cnt,query(o<<1,l,mid,L,R));
    if(R>mid) cnt=minx(cnt,query(o<<1|1,mid+1,r,L,R));
    return cnt;
}

inline void read(int &x) {
    int f=1;x=0;char c=getchar();
    while(c>'9'||c<'0') {if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9') x=10*x+c-48,c=getchar();
    x=x*f;
}

int main(){
    //freopen("hotel.in","r",stdin);
    //freopen("hotel.out","w",stdout);
    
    read(n);read(k);read(p);
    for(int i=1;i<=n;i++){
        read(cc);read(cost[i]);
        color[cc][++color[cc][0]]=i;
    }
    
    build(1,1,n);
    
    for(int i=0;i<k;i++)
        for(int j=1;j<color[i][0];j++){
            tmp1=color[i][j];
            for(int q=j+1;q<=color[i][0];q++){
            	tmp2=color[i][q];
            	if(query(1,1,n,tmp1,tmp2)<=p){
            		ans+=color[i][0]-q+1;
            		break;
            	}
            }
        }
    
    printf("%lld",ans);
    return 0;
}




T3   >>



大模拟!

第一次第一次看到这题目是几个月前 , 然而做不来 , 测试的时候慌了 —— 以为也没写出来 。

冷静想了一下就是一个 Dfs !我就快写出来了 , 就是卡在了消除方块和方块下落的函数上。

原来想的是消除操作又需要用 Dfs , 去寻找横纵相连的方块 , 然而死活想不出来怎么判断该方块是否应该删除 , 所以就没有写出来……

然而事实上消除操作跟根本不用 Dfs , 直接用数组判断三个连续的就打标记 , 消除一次再下落一次 。

但是没有完 ,由于下落的方块或许又将组成一个新的可消除的图 , 我们必须一直 消除 + 下落 一直到不可消除为止!

伪代码:

  while(消除成立) Do 下落 ;

消除时需要用另一个零时数组记录某一个点是否会被删除 , 而不能见三个删三个 , 因为有可能三个方块在原数组的消失会导致后面方块的漏删 , 所以应当打标记 , 最后再改变游戏界面的原数组 。

还有一个需要注意的地方 , 一开始我是用被交换的点坐标作为函数传递成员的 , 然而用一个 5 * 7 的二维数组去存储当前游戏局面 , 每一次操作得到新的局面后 , 进行判断是否已经完成游戏 , 并且步数规定 , 再存储一个坐标数组 , 记录每次交换的方块作为输出就好了 。 

#include<bits/stdc++.h>

using namespace std;

int ans[10][5];
int n,x;

struct node{
    int game[6][8];
}
ori,tmp1,tmp2;

bool delet(){
    node tmpd;
    bool ok=false;
    for(int i=1;i<=5;i++)
        for(int j=1;j<=7;j++)
            tmpd.game[i][j]=0;
    
    for(int i=1;i<=3;i++)
        for(int j=1;j<=7;j++)
            if(ori.game[i][j]>0 && ori.game[i][j]==ori.game[i+1][j] && ori.game[i+1][j]==ori.game[i+2][j])
            	tmpd.game[i][j]=tmpd.game[i+1][j]=tmpd.game[i+2][j]=1;
    
    for(int i=1;i<=5;i++)
        for(int j=1;j<=5;j++)
            if(ori.game[i][j]>0 && ori.game[i][j]==ori.game[i][j+1] && ori.game[i][j+1]==ori.game[i][j+2])
            	tmpd.game[i][j]=tmpd.game[i][j+1]=tmpd.game[i][j+2]=1;
    
    for(int i=1;i<=5;i++)
        for(int j=1;j<=7;j++)
        	if(tmpd.game[i][j]){
            	ori.game[i][j]=0;
            	ok=true;
            }
    return ok;
}

void down()
{
    for(int i=1;i<=5;i++)
        for(int j=2;j<=7;j++)
            if(ori.game[i][j]>0){
                int k=j-1;
                while(k>0 && ori.game[i][k]==0){
                    ori.game[i][k]=ori.game[i][k+1];
                    ori.game[i][k+1]=0;
                    k--;
                }
            }
    for (int i=1;i<=5;i++){
        ori.game[i][0]=0;
        for(int j=1;j<=7;j++)
            if (ori.game[i][j]) ori.game[i][0]++;
            else break;
    }
}

void maintain(){
    down();
    while(delet())
        down();
}

void swapx(int &a,int &b){
    a^=b^=a^=b;
}

void dfs(int stp){
    node tmpx=ori;
    
    if(stp==n+1){
        for(int i=1;i<=5;i++)
            if(ori.game[i][0])
                return ;
        for(int i=1;i<=n;i++)
            printf("%d %d %d\n",ans[i][0]-1,ans[i][1]-1,ans[i][2]);
        exit(0);
    }
    
    for(int i=1;i<=5;i++)
        for(int j=1;j<=ori.game[i][0];j++){
        	if(i<5){
        		swapx(ori.game[i][j],ori.game[i+1][j]);
        		maintain();
        		ans[stp][0]=i;
        		ans[stp][1]=j;
        		ans[stp][2]=1;
        		dfs(stp+1);
        		ori=tmpx;
        	}
        	//right
        	if(i>1 && ! ori.game[i-1][j]){
        		swapx(ori.game[i][j],ori.game[i-1][j]);
        		maintain();
        		ans[stp][0]=i;
        		ans[stp][1]=j;
        		ans[stp][2]=-1;
        		dfs(stp+1);
        		ori=tmpx;
        	}
        	//left
        }
}

int main(){
    ios::sync_with_stdio(false);
    
//	freopen("mayan.in","r",stdin);
  //	freopen("mayan.out","w",stdout);
    
    cin>>n;
    
    for(int i=1;i<=5;i++){
        while(1){
            cin>>x;
            if(!x) break;
            ori.game[i][++ori.game[i][0]]=x;
        }
    }
    dfs(1);
    printf("-1");
    return 0;
}




Day 2   >>>


 


T1   >>


数论到 Day2 , 可以的 ;

推一下:

最后的系数是:dx * ( a^n ) * ( b ^ n ) ; 

而 dx 就是简单的杨辉三角上 k+1 行 m+1 列的系数 , 直接递推 , O (n * m)预处理 + 对 a , b 进行快速幂 (虽然完全没有必要) ;

但是递推杨辉三角的时候写的递归 , 真垃圾 , 我居然忘了些记忆化 ! 然后 T 得圆圆满满 !

好吧QwQ , 看代码 (前方高能 , 代码风格从 4 月份省选后陡变 —— 空格取消后摇):

#include <cstdio>

const  int  mod = 10007 , N = 1000 + 5 ;

long long  x [ N ] [ N ] ;

int  fast ( int  a , int  n ) {
    int  s = 1 ;
    while( n ) {
        if ( n & 1 )
            s = ( s * a ) % mod;
        a = ( a * a ) % mod;
        n = n >> 1 ; 
    }
    return  s ;
}

long long  work ( int  a , int  b ) {
    for ( int  i = 1 ; i <= a ; i ++ ) {
        x [ i ] [ 1 ] = 1 ;
        for ( int  j = 2 ; j < i ; j ++ )
            x [ i ] [ j ] = ( x [ i - 1 ] [ j - 1 ] % mod + x [ i - 1 ] [ j ] % mod ) % mod ;
        x [ i ] [ i ] = 1 ;
    }
    return  x [ a ] [ b ] % mod ;
}

int main(){

    int  a , b , k , m , n ;
    scanf ( "%d%d%d%d%d" , & a , & b , & k , & m , & n ) ;
    a %= mod ;
    b %= mod ;
    int  ans = ( ( work ( k + 1 , m + 1 ) % mod ) * ( fast ( a , m ) % mod ) % mod * fast ( b , n ) ) % mod ;
    printf( "%d" , ans % mod);
    return 0;
}


T2   >>


这题 …… 我又用了线段树 , 然而 T 了 10 个点 , 我算了一下复杂度 , 天 , M * log^2 N , 大约是 5 . 74 * 10^7 刚好被卡 , 而且线段树均摊玄学 , 所以 T 是理所当然的 ;

在自己的 Cena 上面测试了一下 , 将时限调成了 10000s , 发现在第 17 个点居然用了 82 . 91 秒!看来线段树真的玄学 ;

这道题很明显是二分答案的 , 在 [ 1 , Max_weight + 1 ] 进行二分 ;(为什么 Max_weight 要 +1,先挖个坑)

再解一下题意 , 我们要求得对于 M 个区间 , 定义 Yi 等于在 第 i 个区间中获得的检查值 , 其值等于 num *(total_value), 其中 num 为满足该区间中 Wj >= W 的元素的个数 , total_value 表示这些元素的 v值总和 ;

可是为什么我要用线段树呢?其实为了找到某一个区间符合题目要求 , 可以用线段树记录最小值最大值 , 那么:

  定义结构体 (伪代码)Node(num , value), 存储 某一个区间得到的 v值 以及 个数 

  如果:最大值比 W 小 , 那么返回一个 (0 ,0 ), 表示这个区间没有一个元素满足大于 W 的条件;

  否则:

  1、 如果最小值比 W 小 , 那么二分寻找连续的区间 , 直到找到满足条件的连续区间为止,然后此时,执行 3 ;

  2、 如果最小值比 W 大 , 且如果当前所分区间不被所需要的区间完全覆盖:那么继续二分区间直到寻找到连续且被包含的区间为止,执行 3(o 表示在线段树上的节点编号);否则直接执行 4 ;

  3、 返回:(左区间 . num + 右区间 . num , 左区间 . value + 右区间 . value);

  4、 返回:(r - l + 1 即区间长度,sum [ o ] );

这是原来的方法, 可惜找不到源代码了;

漏洞在于如果每连续3个物品的重量呈 高-低-高 或者 低-高-低 , 复杂度近似 O ( n ) ;难怪被卡 ;


其实正确的做法只需要将线段树的查询替换成for一遍记录前缀和就行了:

对于整个区间 N for一遍, 求出所有满足重量 大于 W 的 物品 value 的前缀和 , 以及满足前面条件的物品的 个数 的前缀和 ; 如果不满足 , 那么前缀和就和 它的前一项相等;最后再 for 一遍 M 求出各个区间的 Yi ——

W 为二分的 mid , 二分原则是:

  求出work()得到的ans , 这是这一次可以得到的 质监值 Y , 那么如果 Y 比 S 大 , 那么二分右区间 , 意义是将产品标准 W 提高来减小 Y 的值;反之同理 , 二分左区间以升高 Y 的值 ;

最后会有 左边的最优 Y 和右边的最优 Y,两者取 min 即可 ;

记得前面挖的坑吗?这是因为:有可能当 W 为 Max_weight 时就是最优解 , 那么二分的时候就不会分到 Max_weight,因为最大的 mid 是 Max_weight - 1;((Max_weight -(Max_weight - 1)) / 2 = Max_weight - 1 ;(注意是整除)) 

看代码:
#include <cstdio>

const  int  N = 400000 + 5;
const  long long  inf = 1e18 ;

long long  sum [ N ] , num [ N ] , S , wg [ N ] , vl [ N ] , maxwg , ans , ans1 = inf , ans2 = inf , all = inf ;
int  lf [ N ] , rg [ N ] , n , m , l , r ; 

long long  minx ( long long  a , long long  b ) {
    return  a < b ? a : b ;
}

long long  maxx ( long long  a , long long  b ) {
    return  a > b ? a : b ;
}

long long  absx ( long long  a ) {
    return  a < 0 ? - a : a ;
}

long long  work ( int  W ) {
    
    long long  cntx = 0 ;
    
    for ( int  i = 1 ; i <= n ; i ++ ) 
        if ( wg [ i ] >= W )
        	sum [ i ] = sum [ i - 1 ] + vl [ i ] , num [ i ] = num [ i - 1 ] + 1 ;
        else 
            sum [ i ] = sum [ i - 1 ] , num [ i ] = num [ i - 1 ] ;
    
    for ( int  i = 1 ; i <= m ; i ++ ) 
        cntx += ( num [ rg [ i ] ] - num [ lf [ i ] - 1 ] ) * ( sum [ rg [ i ] ] - sum [ lf [ i ] - 1 ] ) ;
    
    return  cntx ;
    
}

int  main  (  ) {
    
    scanf ( "%d%d%lld" , & n , & m , & S ) ;
    
    for ( int  i = 1 ; i <= n ; i ++ ) { 
        scanf ( "%lld%lld" , & wg [ i ] , & vl [ i ] ) ;
        maxwg = maxx ( maxwg , wg [ i ] ) ;
    }
    for ( int  i = 1 ; i <= m ; i ++ ) 
        scanf ( "%d%d" , & lf [ i ] , & rg [ i ] ) ;
    l = 1 ;  r = maxwg + 1 ;
    while ( l < r ) {
        int  mid = l + r >> 1 ; 
        ans = work ( mid ) ;
        if ( ans > S )  ans1 = ans , l = mid + 1 ;
        else  ans2 = ans , r = mid ;
    }
    
    all = minx ( absx ( S - ans1 ) , absx ( S - ans2 ) ) ;
    
    printf ( "%lld" , all ) ;
    
    return  0 ; 
}



T3   >>


一开始以为是贪心,就是对于需要加速的地方尽量多的加速,但是需要加速的地方就是经过人最多的地方吗?

不是的,试想一下:对于某一个景点来说,如果公交车到达它的时间比人到的时间要早,那么再怎么对之前的路段加速还是会导致在这个景点滞留到同样的时间才能再次出发;相反,对于公交车到达的时间比该景点最迟到达的乘客要晚,那么就可以通过加速之前的某一段使时间变短 。

所以需要加速的路段一定是该公交车到达的时间比景点最迟到达的乘客要晚的路段;再想一下,对于这种路段可能存在很多种,我们可以记录一个 nxt [ i ] 数组,它的意义在于从 i + 1 站开始到 nxt [ i ] - 1 这一段可以通过加速使得时间减小;那么如何选择加速一段使减小的时间更多呢?

可以这样想,假设两种加速可以使某两段分别收益,并且受益人群一定为从 某点 i 出发然后到 nxt [ 某点 i ] 这个区间中的所有要下车的人(因为不再这个区间下车的人可能会在后面被卡住,因此无法收益);那么通过在 X 段加速减小的时间更多的条件是从 X 的首点 出发然后到 nxt [ X 首点 ] 这个区间中的所有要下车的人  比  Y 段的从 Y 的首点 出发然后到 nxt [ Y 首点 ] 这个区间中的所有要下车的人更多 。我们可以通过前缀和实现:

实现前缀和
 
low 用来寻找到底加速哪一段更优,并且保证 Di 大于 0;它的默认值是第一个 Di 大于 0 的位置 。

找出了需要删除的段 [ low + 1 , nxt [ low ] - 1 ] 之后,我们可以贪心的减小氮气的使用量,也就是给后面尽量多的氮气,并且保证这次加速能够让哪怕一个人的时间减小。

由于加速之后的局面可能存在比整段一起加速更优的情况,所以这次删除的量是最小的汽车到达时间和乘客到达时间的时间差 。

看代码:
#include <cstdio>

const  int  N = 200000 + 5;
const  long long  inf = 1e18 ;

long long  sum [ N ] , ans , low , delta , lst [ N ] , nxt [ N ] , arv [ N ] ;
int  n , m , k , T [ N ] , D [ N ] , A [ N ] , B [ N ] ;

long long  maxx ( long long  a , long long  b ) {
    return  a > b ? a : b ;
}

long long  minx ( long long  a , long long  b ) {
    return  a < b ? a : b ;
}

int  main  (  ) {
    
    scanf ( "%d%d%d" , & n , & m , & k ) ;
    
    for ( int  i = 1 ; i < n ; i ++ ) scanf ( "%d" , & D [ i ] ) ;
    
    for ( int  i = 1 ; i <= m ; i ++ ) {
        scanf ( "%d%d%d" , & T [ i ] , & A [ i ] , & B [ i ] ) ;
        lst [ A [ i ] ] = maxx ( lst [ A [ i ] ] , T [ i ] ) ;
        sum [ B [ i ] ] ++ ;
    }
    for ( int  i = 1 ; i <= n ; i ++ )
        sum [ i ] += sum [ i - 1 ] ;
    
    while ( 1 ) {
        low = 1 ;
        arv [ 1 ] = 0 ;
        for ( int  i = 2 ; i <= n ; i ++ )
            arv [ i ] = maxx ( lst [ i - 1 ] , arv [ i - 1 ] ) + D [ i - 1 ] ; // D [ i -1 ] 惨痛教训 
        nxt [ n ] = n ;
        for ( int  i = n - 1 ; i >= 1 ; i -- ) {
            nxt [ i ] = nxt [ i + 1 ] ;
            if ( arv [ i + 1 ] <= lst [ i + 1 ] ) // 惨痛教训! i+1 ! 
                nxt [ i ] = i + 1 ;// if it dosen't fit the condition , then change the most valuable place to i+1 , which can not be benifit from th nitro speed up
        }
        
        low = 1 ;
        while ( ( ! D [ low ] )  &&  low < n )
            low ++ ;
        
        if ( low == n  ||  ! k )
            break ;
        
        for ( int  i = low + 1 ; i < n ; i ++ )
            if ( D [ i ]  &&  sum [ nxt [ low ] ] - sum [ low ] < sum [ nxt [ i ] ] - sum [ i ] )
                low = i ;
        
        if ( sum [ nxt [ low ] ] - sum [ low ] == 0 )
            break ;
        
        delta = inf ;
        
        for ( int  i = low + 1 ; i <= nxt [ low ] - 1 ; i ++ )
            delta = minx ( delta , arv [ i ] - lst [ i ] ) ;
        
        delta = minx ( delta , k ) ;
        delta = minx ( delta , D [ low ] ) ;
        k -= delta ;
        D [ low ] -= delta ;
    }
    
    for ( int  i = 1 ; i <= m ; i ++ )
        ans += arv [ B [ i ] ] - T [ i ] ; 
    
    printf  ( "%lld" , ans ) ;
    
    return  0 ; 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值