NOIP2011 复盘

NOIP2011 复盘

D1T1 P1003 铺地毯

经典题目,不必多说

#include
using std::cin;
using std::cout;
using std::endl;
const int maxn = 10005;
int a[maxn], b[maxn], g[maxn], k[maxn], n;
int x, y;
int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) scanf("%d %d %d %d", &a[i], &b[i], &g[i], &k[i]);
    scanf("%d %d", &x, &y);
    for(int i = n; i >= 1; i--) {
        if(a[i] <= x && x <= a[i] + g[i] && b[i] <= y && y <= b[i] + k[i]) {
            printf("%d\n", i);
            return 0;
        }
    }
    printf("-1\n");
    return 0;
}

D1T2 P1311 选择客栈

对于最低消费,套一个ST表求RMQ。

我们将相同的色调的点一起存,然后考虑用暴力跑出答案。

然后可以发现,如果左端点固定,只需要按顺序向右找第一个满足条件的点,后面的点来组成一定都可以。

然后不知道为什么就过了。。。数据水

急需学习新解法!

#include
const int maxn = 200005;
int n, k, p;
int c[maxn], a[maxn];
int st[maxn][21];
std::vector colors[55];

void init() {
    for(int i = 1; i <= n; i++) st[i][0] = a[i];
    for(int j = 1; j <= 20; j++) {
        for(int i = 1; i + (1 << j) - 1 <= n; i++) {
            st[i][j] = std::min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
        }
    }
}
int query(int l, int r) {
    int k = 0;
    while((1 << (k + 1)) < r - l + 1) k++;
    return std::min(st[l][k], st[r - (1 << k) + 1][k]);
}
int main() {
    scanf("%d %d %d", &n, &k, &p);
    for(int i = 1; i <= n; i++) {
        scanf("%d %d", &c[i], &a[i]);
        colors[c[i]].push_back(i);
    }
    init();
    int ans = 0;
    for(int i = 0; i < k; i++) {
        for(int j = 0; j < colors[i].size(); j++) {
            for(int k = j + 1; k < colors[i].size(); k++) {
                if(query(colors[i][j], colors[i][k]) <= p) {
                    ans += colors[i].size() - k;
                    break;
                }
            }
        }
    }
    printf("%d\n", ans);
    return 0;
}

题解里面有一个解法十分简洁:

#include 
#define maxn 200005
using namespace std;
int n,k,p;
int color,price;
int last[maxn];
int sum[maxn];
int cnt[maxn];
int ans = 0;
int now;
int main(){
    cin >> n >> k >> p;
    for (int i=1;i<=n;i++){
        cin >> color >> price;
        if (price <= p)
            now = i;
        if (now >= last[color])
            sum[color] = cnt[color];
        last[color] = i;
        ans += sum[color];
        cnt[color]++;
    }
    cout << ans << endl;
    return 0;
}

代码通过记录前面有多少与之同色调而且包含了可以住下的now的点数,只要一次遍历就可以解决。nb!

D1T3 P1312 Mayan游戏

这道题是真的难,既难骗分又难写正解。。

显然我们一定要套dfs的思路,在固定搜\(n\)步中搜到所有方块都被消掉。

可以发现,这道题的重要动作是:交换两个方块,悬浮方块掉下,同色方块消除,悬浮方块掉下,同色方块消除,……

所以我们大的方向是:每一步搜交换哪两个方块,然后掉落,接下来进入循环,若能消就消,然后继续掉落,若不能消则一次操作结束。

我们接下来考虑如何写掉落函数:我自己有一种递推的方法可以计算出悬浮方块的掉落后位置。

dp[i][j]为悬浮方块中\(i\)\(j\)列将会掉到的行数(从下开始计数),若下面刚好为空,则暴力找到掉下的位置,正上面的方块只需要继承这个dp值然后再加1就可以了。

代码(抄题解的)中的掉落更聪明,直接用一个变量维护了,可以参考下。

消除是个难点,因为还有图5那种莫名其妙很难实现的消除,我们看看能够怎么解决:(注意:这个函数只代表一次消除,你可能要消除很多遍)

我们枚举一个点,如果左右都有同色的那横的就能消除,再暴力探索最左和最右同色的色块,都打上标记。上下消除的也是同理。最后将所有打过标记的都消除,再让他们掉落就是了。

如果整个状态找了一遍都没有出现上述任何一次三个以上的,就是没能消,一次操作就结束。

但是如果直接这么写还过不了。在交换的时候有很多的浪费,可以剪枝:

  1. 交换两个相同的色块没有意义。
  2. 左边的主动跟右边交换比右边主动跟左边交换更优,所以不往左边交换,除非左边为空才向左移。

代码:

/*************************************************************************
 @Author: Garen
 @Created Time : Wed 30 Jan 2019 12:53:57 PM CST
 @File Name: P1312.cpp
 @Description:
 ************************************************************************/
#include
using std::cin;
using std::cout;
using std::endl;
const int maxn = 6, maxm = 8;
int G[maxn][maxm];
int backup[maxn][maxn][maxm];
struct Nodes {
    int x, y, g;
    Nodes(int x, int y, int g): x(x), y(y), g(g) {}
};
std::vector answers;
bool vis[maxn][maxm];
int n;

void copy_one(int t) {
    for(int i = 1; i <= 5; i++) for(int j = 1; j <= 7; j++) backup[t][i][j] = G[i][j];
}
void copy_two(int t) {
    for(int i = 1; i <= 5; i++) for(int j = 1; j <= 7; j++) G[i][j] = backup[t][i][j];
}
bool find() {
    bool flag = false;
    for(int i = 1; i <= 5; i++) {
        for(int j = 1; j <= 7; j++) {
            if(G[i][j] && i - 1 >= 1 && i + 1 <= 5 && G[i][j] == G[i + 1][j] && G[i][j] == G[i - 1][j]) {
                flag = true;
                vis[i][j] = vis[i - 1][j] = vis[i + 1][j] = true;
                for(int k = i - 2; k >= 1; k--) {
                    if(G[i][j] == G[k][j]) vis[k][j] = true;
                    else break;
                }
                for(int k = i + 2; k <= 5; k++) {
                    if(G[i][j] == G[k][j]) vis[k][j] = true;
                    else break;
                }
            }
            if(G[i][j] && j - 1 >= 1 && j + 1 <= 7 && G[i][j] == G[i][j - 1] && G[i][j] == G[i][j + 1]) {
                flag = true;
                vis[i][j] = vis[i][j - 1] = vis[i][j + 1] = true;
                for(int k = j - 2; k >= 1; k--) {
                    if(G[i][j] == G[i][k]) vis[i][k] = true;
                    else break;
                }
                for(int k = j + 2; k <= 7; k++) {
                    if(G[i][j] == G[i][k]) vis[i][k] = true;
                    else break;
                }
            }
        }
    }
    if(!flag) return false;
    for(int i = 1; i <= 5; i++) {
        for(int j = 1; j <= 7; j++) {
            if(vis[i][j]) {
                vis[i][j] = G[i][j] = 0;
            }
        }
    }
    return true;
}
void update() {
    for(int i = 1; i <= 5; i++) {
        int now = 0;
        for(int j = 1; j <= 7; j++) {
            if(!G[i][j]) now++;
            else {
                if(!now) continue;
                G[i][j - now] = G[i][j]; G[i][j] = 0;
            }
        }
    }
}
bool empty() {
    for(int i = 1; i <= 5; i++) if(G[i][1]) return false;
    return true;
}
void dfs(int t) {
    if(t == n) {
        if(empty()) {
            for(auto it : answers) cout << it.x << ' ' << it.y << ' ' << it.g << endl;
            exit(0);
        }
        return;
    }
    copy_one(t);
    for(int i = 1; i <= 5; i++) {
        for(int j = 1; j <= 7; j++) {
            if(G[i][j]) {
                // right
                if(i + 1 <= 5 && G[i][j] != G[i + 1][j]) {
                    std::swap(G[i][j], G[i + 1][j]);
                    answers.push_back(Nodes(i - 1, j - 1, 1));
                    update();
                    while(find()) update();
                    dfs(t + 1);
                    copy_two(t);
                    answers.pop_back();
                }
                // left
                if(i - 1 >= 1 && G[i - 1][j] == 0) {
                    std::swap(G[i][j], G[i - 1][j]);
                    answers.push_back(Nodes(i - 1, j - 1, -1));
                    update();
                    while(find()) update();
                    dfs(t + 1);
                    copy_two(t);
                    answers.pop_back();
                }
            }
        }
    }
}
int main() {
    cin >> n;
    for(int i = 1; i <= 5; i++) {
        int temp;
        for(int j = 1; ; j++) {
            cin >> temp;
            if(temp) G[i][j] = temp;
            else break;
        }
    }
    dfs(0);
    cout << -1 << endl;
    return 0;
}

D2T1 P1313 计算系数

这道题是数学题,算就完事了。

根据2-3学的二项式定理,就能够发现\(x^n y^m\)项的系数就是\(C_k^n b^m a^n\)

因为数据都很小,直接用ksm和暴力组合数算法就能求出答案了。

D2T2 P1314 聪明的质监员

经典的二分题。

显然,\(W\)越大\(Y\)越小,\(W\)越小\(Y\)越大,要想\(Y\)\(S\)附近,就必须控制\(W\)在一定区间范围。

所以我们直接二分\(W\),算出当前\(W\)下对应的\(Y\),若大了就增大\(W\),若小了就减少,这样就会越来越精确。

但是如何高效地求出对应的\(Y\)值?我们用两个前缀和维护两个\(\sum\)的值,\(O(n)\)滚一遍之后每个\(Y_i\)就都可以\(O(1)\)求出来了。

所以复杂度是完美的\(O(n \log n)\)

#include
using std::cin;
using std::cout;
using std::endl;
#define ll long long
const ll maxn = 200005;
ll w[maxn], v[maxn];
ll L[maxn], R[maxn];
ll n, m, S;
ll a[maxn], b[maxn];
ll Y;
bool check(ll mid) {
    a[0] = b[0] = 0;
    for(int i = 1; i <= n; i++) {
        a[i] = a[i - 1] + (w[i] >= mid);
        b[i] = b[i - 1] + (w[i] >= mid ? v[i] : 0);
    }
    Y = 0;
    for(int i = 1; i <= m; i++) {
        Y += (a[R[i]] - a[L[i] - 1]) * (b[R[i]] - b[L[i] - 1]);
    }
    //cout << mid << ": " << res << endl;
    if(S - Y >= 0) {
        return true;
    }
    else return false;
}
int main() {
    cin >> n >> m >> S;
    ll left = 1e16, right = 0;
    for(int i = 1; i <= n; i++) {
        cin >> w[i] >> v[i];
        left = std::min(left, w[i]); right = std::max(right, w[i]);
    }
    for(int i = 1; i <= m; i++) cin >> L[i] >> R[i];
    ll ans = 0x3f3f3f3f3f3f3f3f;
    left -= 1; right += 2;
    while(left < right) {
        ll mid = (left + right) / 2;
        if(check(mid)) right = mid;
        else left = mid + 1;
        ans = std::min(ans, llabs(S - Y));
    }
    //cout << abs(S - ANS) << endl;
    cout << ans << endl;
    return 0;
}

D2T3 P1315 观光公交

个人错误:把\(k\)放在一起考虑,无法做到真正的贪心

这是一道不一样的贪心。。。

首先给出数据范围:\(n \leq 1000,m \leq 10000,k \leq 100000\)

题目的模拟部分非常简单,就是普通的上车和下车,将所有人所用的时间相加即可得到答案。

不难发现成功的模拟用到的复杂度是\(O(n)\)的。

部分分给了我们提示,当\(k=1\)时,我们一定会选择那条经过人最多的路。

但是\(k>1\)时,很难协调每个方案之间的关系。

其实,只要把每一次加速器当做最后一次加速器,每次找到加速一次的最佳路径,重复做\(k\)遍即可。 可以发现这样做一定是最优的。

为什么\(k\)复杂度那么大还能重复做\(k\)遍啊?因为\(O(kn)\)没有超时!

题解里面用到了一个range数组,挺有精髓的,这里解释一下:

range[i]代表当前状况,如果在第\(i\)条路用了加速器,会从\(i+1\)影响到哪里。

计算range[i]采用了倒推,如果一个站的时候车不用等人,就直接继承后面,否则就到此为止。

代码很有细节:

#include
const int maxn = 10005;
const int INF = 0x3f3f3f3f;
int n, m, k;
struct Nodes {
    int tim, s, t;
} s[maxn];
int latest[maxn];
int tim[maxn];
int range[maxn];
int ans;
int d[maxn];
int sum[maxn];
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", &s[i].tim, &s[i].s, &s[i].t);
        latest[s[i].s] = std::max(latest[s[i].s], s[i].tim);
        sum[s[i].t]++;
    }
    for(int i = 1; i <= n; i++) sum[i] += sum[i - 1];
    for(int i = 1; i < n; i++) {
        tim[i + 1] = std::max(tim[i], latest[i]) + d[i];
    }
    for(int i = 1; i <= m; i++) {
        ans += tim[s[i].t] - s[i].tim;
    }
    while(k--) {
        int maxv = 0, idx = -1;
        range[n - 1] = n;
        for(int i = n - 2; i >= 1; i--) {
            if(tim[i + 1] > latest[i + 1]) range[i] = range[i + 1];
            else range[i] = i + 1;
        }
        for(int i = 1; i < n; i++) {
            if(sum[range[i]] - sum[i] > maxv && d[i]) {
                maxv = sum[range[i]] - sum[i]; idx = i;
            }
        }
        if(maxv == 0) break;
        ans -= maxv;
        d[idx]--;
        tim[1] = 0;
        for(int i = 1; i < n; i++) {
            tim[i + 1] = std::max(tim[i], latest[i]) + d[i];
        }
    }
    printf("%d\n", ans);
    return 0;
}

转载于:https://www.cnblogs.com/Garen-Wang/p/11333401.html

你可能感兴趣的:(NOIP2011 复盘)