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那种莫名其妙很难实现的消除,我们看看能够怎么解决:(注意:这个函数只代表一次消除,你可能要消除很多遍)
我们枚举一个点,如果左右都有同色的那横的就能消除,再暴力探索最左和最右同色的色块,都打上标记。上下消除的也是同理。最后将所有打过标记的都消除,再让他们掉落就是了。
如果整个状态找了一遍都没有出现上述任何一次三个以上的,就是没能消,一次操作就结束。
但是如果直接这么写还过不了。在交换的时候有很多的浪费,可以剪枝:
- 交换两个相同的色块没有意义。
- 左边的主动跟右边交换比右边主动跟左边交换更优,所以不往左边交换,除非左边为空才向左移。
代码:
/*************************************************************************
@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;
}