20170218 机房『NOIP2010』

20170218 机房『NOIP2010』

科学家不是依赖于个人的思想,而是综合了几千人的智慧,所有的人想一个问题,并且每人做它的部分工作,添加到正建立起来的伟大知识大厦之中。
—— 卢瑟福

我热爱生活,我是一名快速成长的OIer

目录:

T1 translate

洛谷P1540 NOIP2010 机器翻译
这是一道大水题,就不贴题了。模拟,使用循环队列求解。
但需要注意的是,单词为非负数,可能为0,需要区别它与空栈。班上很多人为此WA了几个点。

/*
User name: Doggu
Time:O(n)
Space:O(n)
*/
#define NAME "translate"
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int OO = 0x3f3f3f3f;
struct IO {
    IO() {
        freopen( NAME".in", "r", stdin );
        freopen( NAME".out", "w", stdout );
    }
    ~IO() {
        fclose( stdin );
        fclose( stdout );
    }
}Doggu;
template<class TE> inline void readin(TE &res) {
    static char ch;
    while((ch=getchar())<'0'||ch>'9');
    res=ch-'0';
    while((ch=getchar())>='0'&&ch<='9') res=(res<<1)+(res<<3)+ch-'0';
}
const int N = 1000 + 10;
const int M = 100 + 10;
int m, n, x, team[M], tot, vis[N], rear;
int main() {
    memset( team, -1, sizeof(team) );
    memset( vis, 0, sizeof(vis) );
    readin(m);
    readin(n);
    tot = 0;
    rear = 0;
    for( int i = 1; i <= n; i++ ) {
        readin(x);
        if( vis[x] ) continue;
        tot++;
        rear = ( rear + 1 ) % m;
        if( team[rear] != -1 )
            vis[team[rear]] = 0;
        vis[team[rear] = x] = 1;
    }
    printf( "%d\n", tot );
    return 0;
}

T2 tortoise

洛谷P1541 NOIP2010 乌龟棋
简单DP,按题模拟,是有套路的。然而,DP什么呢?
开始时,我已经蒙了,甚至试图使用状压,但其实根本不需要。因为卡牌只有四类,且用完以后刚好就能到终点。
于是,我改变了策略,但还是想得很复杂,企图把距离DP进去。虽然可以,但转移方程很不简明,复杂度较高且易错,于是最终WA完了。
其实只需要DP四种牌的数目就够了。

#define NAME "tortoise"
#define res( x, y ) for( x = 0; x <= y; x++ )
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int OO = 0x3f3f3f3f;
struct IO {
    IO() {
        freopen( NAME".in", "r", stdin );
        freopen( NAME".out", "w", stdout );
    }
    ~IO() {
        fclose( stdin );
        fclose( stdout );
    }
}Doggu;
template<class TE> inline void readin(TE &res) {
    static char ch;
    while((ch=getchar())<'0'||ch>'9');
    res=ch-'0';
    while((ch=getchar())>='0'&&ch<='9') res=(res<<1)+(res<<3)+ch-'0';
}
const int N = 350 + 10;
const int M = 120 + 10;
const int K = 40 + 1;
int n, m, x, y, z, w, aa[N], b[5];
int f[K][K][K][K];//DP使用牌的数目
int main() {
    readin(n);
    readin(m);
    for( int i = 1; i <= n; i++ )
        readin(aa[i]);
    for( int j = 1; j <= m; j++ ) {
        readin(x);
        b[x]++;
    }
    res(x,b[1])res(y,b[2])res(z,b[3])res(w,b[4]) {
        int M = 0;
        if(x-1>=0) M = max(M,f[x-1][y][z][w]);
        if(y-1>=0) M = max(M,f[x][y-1][z][w]);
        if(z-1>=0) M = max(M,f[x][y][z-1][w]);
        if(w-1>=0) M = max(M,f[x][y][z][w-1]);
        f[x][y][z][w] = M + aa[1+x+2*y+3*z+4*w];
    }
    printf( "%d\n", f[b[1]][b[2]][b[3]][b[4]] );
    return 0;
}

T3 prison

洛谷P1525 NOIP2010 关押罪犯
此题贴两解。
法1:二分答案+DFS(二分图染色) O(nlogn)
我在做此题时,DFS内部逻辑关系没有理清楚,为此WA完了。
在二分答案时,用DFS遍历一遍图,若边权无伤大雅则忽略。然后,若颜色相同则返回false,若未染色则进行拓展。

#define NAME "prison"
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int OO = 0x3f3f3f3f;
struct IO {
    IO() {
        freopen( NAME".in", "r", stdin );
        freopen( NAME".out", "w", stdout );
    }
    ~IO() {
        fclose( stdin );
        fclose( stdout );
    }
}Doggu;
template<class TE> inline void readin(TE &res) {
    static char ch;
    while((ch=getchar())<'0'||ch>'9');
    res=ch-'0';
    while((ch=getchar())>='0'&&ch<='9') res=(res<<1)+(res<<3)+ch-'0';
}
const int N = 20000 + 100;
const int M = 100000 * 2 + 1000;

struct Edge {
    int v, w, next;
}t[M];
int head[N], num = 0;
inline void insert( int u, int v, int w ) {
    t[++num]. v = v;
    t[num]. w = w;
    t[num]. next = head[u];
    head[u] = num;
}
int volk[N];
bool dfs( int u, int f, int limit ) {
    for( int i = head[u]; i; i = t[i].next ) {
        int v = t[i].v;
        if( v == f ) continue;
        if( t[i].w > limit ) {
            if( volk[v] == volk[u] ) return false;
            if( !volk[v] ) 
                if( !dfs( v, u, limit ) )return false;
        }
    }
    return true;
}
int n, m, maxw;
int main() {
    readin( n );
    readin( m );
    maxw = 0;
    for( int i = 1; i <= m; i++ ) {
        int u, v, w;
        readin( u );
        readin( v );
        readin( w );
        maxw = max(maxw,w);
        insert( u, v, w );
        insert( v, u, w );
    }
    int L = 0, R = maxw;
    while( L != R ) {
        memset( volk, 0, sizeof(volk) );
        int mid = ( L + R ) >> 1, ok = 1;
        for( int i = 1; i <= n; i++ )
            if( !volk[i] ) {
                volk[i] = 1;
                ok = dfs( i, i, mid );
                if( !ok ) break;
            }
        if( ok ) R = mid;
        else L = mid + 1;
    }
    printf( "%d\n", L );
    return 0;
}

法2:并查集,用n+i表示i的反人。先排序,再从大到小。 O(nlogn)
这是一种很神奇的思想,先从大到小排序,处理每一对关系。
若fa[i]=fa[j],则表示i与j同狱。
若fa[i]=fa[n+j](fa[j]=fa[n+i]),则表示i与j不同狱。
只要出现同狱,就输出当前关系仇恨值,可以保证是最小的(贪心思想)。
提供者:lemonoil

#include <cstdio>
#include <algorithm>
using namespace std;
struct N {
    int x, y, w;
}a[100000+100];
int n, m, fa[20000*2+100];
bool comp( const struct N &a, const struct N &b ) {
    return a.w > b.w;
}
int find( int x ) {
    if( fa[x] == x ) return x;
    return fa[x] = find(fa[x]);
}
int main() {
    freopen( "prison.in", "r", stdin );
    freopen( "prison,out", "w", stdout );
    scanf( "%d%d", &n, &m );
    for( int i = 1; i <= m; i++ )
        scanf( "%d%d%d", &a[i].x, &a[i].y, &a[i].w );
    for( int i = 1; i <= 2*n; i++ )
        fa[i] = i;
    sort( a+1, a+m+1, comp );
    for( int i = 1; i <= m; i++ ) {
        int fax = find(a[i].x), fay = find(a[i].y);
        if( fax == fay ) {
            printf( "%d\n", a[i].w );
            return 0;
        }
        fa[fax] = find( a[i].y + n );
        fa[fay] = find( a[i].x + n );
    }
    printf( "0\n" );
    return 0;
}

T4 flow

洛谷P1514 NOIP2010 引水入城
这一道题,不算太难。但当然,考场上我没有做出来。刚看见这道题时简直蒙了,但细细思考还是离正确答案更近了一步。
不过,自己做的题还是不够,很难在短时间内自己把模型给建立出来。幸好,伟大的文学家、哲学家、社会运动家Scanf Hu曾多次耳濡目染,多写暴力是人类进步的阶梯。
又不过,对于这道题,我连暴力都写不出来。于是,仔细分析数据过后,我采用特判,解决了3个不能满足要求的数据点,得到了30分。

而下面贴的是正解。我们从题目的该图片中,可以得到一点启示。
引水入城
第一排有m个位置可建蓄水厂,我们可以证明,如果能满足要求,每一个建成的蓄水厂都可以对应一段连续区间的城市(使用反证法)。这一切,只需要一个DFS以 O(n) 的复杂度来解决。
而我们所要做的,便是从这m个中挑选出最少的点,使得它们能够覆盖整个区间,这就是区间覆盖问题。对此我们仅需套一个简单DP,下面给出了两种解法。
由于该题目的性质,最终挑选出的结点一定能包含自己正下方的点,可以凭此进行一点小优化。

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int OO = 0x3f3f3f3f;
template<class TE> inline void readin(TE &res) {
    static char ch;
    while((ch=getchar())<'0'||ch>'9');
    res=ch-'0';
    while((ch=getchar())>='0'&&ch<='9') res=(res<<1)+(res<<3)+ch-'0';
}
const int N = 500 + 10;
const int M = 500 + 10;
int n, m, height[N][M], l[N][M], r[N][M], dp[M];
bool vis[N][M];

void dfs( int i, int j );
void search( int i, int j, int x, int y ) {
    if( x >= 1 && x <= n && y >= 1 && y <= m && height[x][y] < height[i][j] ) {
        if( !vis[x][y] ) dfs( x, y );
        l[i][j] = min(l[i][j],l[x][y]);
        r[i][j] = max(r[i][j],r[x][y]);
    }
}
void dfs( int i, int j ) {
    vis[i][j] = 1;
    int x = i - 1, y = j;
    search( i, j, x, y );
    x = i + 1, y = j;
    search( i, j, x, y );
    x = i, y = j - 1;
    search( i, j, x, y );
    x = i, y = j + 1;
    search( i, j, x, y );
}

int main() {
    freopen( "flow.in", "r", stdin );
    freopen( "flow.out", "w", stdout );
    readin(n);
    readin(m);
    for( int i = 1; i <= n; i++ )
        for( int j = 1; j <= m; j++ )
            readin( height[i][j] );
    memset( l, OO, sizeof(l) );
    memset( r, 0, sizeof(r) );
    memset( dp, OO, sizeof(dp) );
    int ans = 0;
    for( int j = 1; j <= m; j++ )
        l[n][j] = r[n][j] = j;
    for( int j = 1; j <= m; j++ )
        if( !vis[1][j] )
            dfs( 1, j );
    for( int j = 1; j <= m; j++ )
        if( !vis[n][j] )
            ans++;
    if( ans != 0 ) {
        printf( "0\n%d\n", ans );
        return 0;
    }
    dp[0] = 0;
    for( int j = 1; j <= m; j++ ) {
        for( int k = j; k <= r[1][j]; k++ )
            for( int p = l[1][j] - 1; p <= j; p++ )
                dp[k] = min(dp[k],dp[p]+1);
    }
    /*
    for( int k = 1; k <= m; k++ )
        for( int j = 1; j <= k; j++ )
            if( k <= r[1][j] )
                for( int p = l[1][j] - 1; p <= j; p++ )
                    dp[k] = min(dp[k],dp[p]+1);
    */
    printf( "1\n%d\n", dp[m] );
    return 0;
}

经验与教训

这是第一次在机房里考历年真题,经验还是很不足。但是,只有凭此去一步步地完善自我,提升自我,才能真正得到质的飞跃。
修改前,只有130,很低很低。加上入学考试的不如意,寒假毕竟学习了20天的竞赛。这一切,差一点就让我崩溃了。
然而,事后再想想,一切其实并没有那么糟。竞赛不过是第7门主科,总会有起起落落。只要相信自己,掌握方法,学会付出,善于总结与创新实践,就一定能让未来的自己感到欣慰。加油!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值