洛谷11月月赛(284pts rank85)
https://www.luogu.org/contestnew/show/12006
我是比赛完后在去写的
这是我第一次打洛谷月赛,之前一次是比赛完才去看而且写了第一题就没写后面的了
284分,太水了,rank85左右
第一题第二题AC了,第三题写了3^n的算法,知道会超时,也知道满分做法应该是只考虑有价值的状态
但是还是没想出正解,拿了70分
第四题想到了讲评中说的错误的做法,然后细节比较多,怒刚1.5h,然而写挂了,交上去14分……
还不如写暴力有30~40分
考场策略出错了,如果最后一道题考虑骗分,能上300(然而300分rank还是很后)
P4994 终于结束的起点
5分钟AC。
签到题,数组都不用开。
写的时候不太确定会不会答案非常大导致超时,但是直接交就AC了
好像有个什么定理可以得出答案不会太大。反正我ac了就好
#include<bits/stdc++.h> #define REP(i, a, b) for(register int i = (a); i < (b); i++) #define _for(i, a, b) for(register int i = (a); i <= b; i++) using namespace std; int main() { int m; scanf("%d", &m); int a = 0, b = 1; int ans = 0; while(1) { ans++; int t = (a + b) % m; a = b; b = t; if(a == 0 && b == 1) { printf("%d\n", ans); break; } } return 0; }
P4995 跳跳!
30分钟AC
看完题脑中闪过贪心的念头,但是后来看到数据范围400,就去想n^3的dp了
然后最后折腾了一番,才发觉来回跳一定比顺着跳要优。
那是不是一直来回跳就是最优的
然后我就猜了最高,最低,次高,次低……
然后就AC了。
大胆猜想!!
#include<bits/stdc++.h> #define REP(i, a, b) for(register int i = (a); i < (b); i++) #define _for(i, a, b) for(register int i = (a); i <= b; i++) using namespace std; typedef long long ll; const int MAXN = 300 + 10; ll dp[MAXN], a[MAXN], ans; int n; int main() { scanf("%d", &n); _for(i, 1, n) scanf("%lld", &a[i]); a[0] = 0; sort(a, a + n + 1); int l = 0, r = n; while(l < r) { ans += (a[r] - a[l]) * (a[r] - a[l]); l++; if(l >= r) break; ans += (a[r] - a[l]) * (a[r] - a[l]); r--; } printf("%lld\n", ans); return 0; }
P4996 咕咕咕
1h10min拿了70分
第一反应就是枚举子集,3^n
但是自己没注意到一些细节,以及没想清楚当前这个状态的价值要乘上方案,所以一直调来调去
一个小时左右才过了样例
#include<bits/stdc++.h> #define add(a, b) a = (a + b) % mod #define REP(i, a, b) for(register int i = (a); i < (b); i++) #define _for(i, a, b) for(register int i = (a); i <= b; i++) using namespace std; typedef long long ll; const int mod = 998244353; const int MAXN = (1 << 20) + 10; int dp[MAXN], a[MAXN], num[MAXN], ans; char s[MAXN]; int n, m; int main() { scanf("%d%d", &n, &m); _for(i, 1, m) { int x = 0, y; scanf("%s%d", s, &y); REP(i, 0, strlen(s)) x = x * 2 + s[i] - '0'; a[x] = y; } dp[0] = a[0]; num[0] = 1; REP(S, 1, 1 << n) { for(int S0 = (S - 1) & S; ; S0 = (S0 - 1) & S) { add(dp[S], dp[S0]); add(num[S], num[S0]); if(!S0) break; } add(dp[S], 1ll * num[S] * a[S]); } printf("%d\n", dp[(1 << n) - 1]); return 0; }
显然会超时
可以只考虑有价值的状态对答案的贡献
显然把包含这个状态的方案数乘以这个状态的价值就是对答案的贡献
那么考虑怎么算方案
比如对于011
所有方案都是从000到011再到111
那么考虑从000到011的方案
显然是把两个0变成两个1的方案
更一般的来说,可以初始化出一个数组,num[i]表示把i个0变成i个1的方案数
考虑把j个0一起变成1
那么有c(i, j) * num[i-j]种方案
那么枚举j就可以了
现在想想其实不难,但是考试的时候就是没想到
做题不够多
#include<bits/stdc++.h> #define add(a, b) a = (a + b) % mod #define REP(i, a, b) for(register int i = (a); i < (b); i++) #define _for(i, a, b) for(register int i = (a); i <= b; i++) using namespace std; typedef long long ll; const int mod = 998244353; const int MAXN = 21; ll c[MAXN][MAXN], num[MAXN], ans; char s[MAXN]; int n, m; void init() { _for(i, 0, 20) { c[i][0] = 1; _for(j, 1, i) add(c[i][j], c[i-1][j-1] + c[i-1][j]); } num[0] = 1; _for(i, 1, 20) _for(j, 1, i) add(num[i], c[i][j] * num[i-j]); } int main() { init(); scanf("%d%d", &n, &m); _for(i, 1, m) { int x = 0, a, cnt = 0; scanf("%s%d", s, &a); REP(i, 0, strlen(s)) cnt += (s[i] == '1'); add(ans, a * num[cnt] % mod * num[n-cnt]); } printf("%lld\n", ans); return 0; }
P4997 不围棋
大模拟……
大模拟就很考细心和码力了
我一开始没想到用并查集去维护这个东西,而想着用搜索
所以代码非常复杂,不能像并查集一样很方便的实现合并操作
方向就错了
然后我改题又改了N久
讲几个关键点
(1)把二维坐标转化为一个数字,下标从1开始
#define ID(x, y) (x - 1) * n + y int xx = (t1 - 1) / n + 1, yy = ((t1 - 1) % n) + 1;
(2)把气的概念改一下。改成可以重复的,也就是说如果一个格子的不同方向有同一个联通块,那就加多次,而不是加一次。这样会非常方便
(3)巧用异或实现黑白棋的转化
(4)可以把边界的格子设为'#',这样很方便
(5)放棋子的时候,先减去周围联通块的气,再加上自己的。
(6)有几种情况下不能放。赌死对方棋子。这个时候判断一下对方棋子的气是不是大于0就好
堵死己方棋子。这个地方尤为重要,我卡了很久。
如果放下去的棋子周围有空格,那么己方棋子一定存活,因为无论如何都有己方棋子的气
如果有一个己方棋子的联通块存活,那么己方棋子一定存活。因为有些可能本来是死的,然后和己方棋子连到一块就活了
因为最后是成一个联通块的
#include<bits/stdc++.h> #define ID(x, y) (x - 1) * n + y #define REP(i, a, b) for(register int i = (a); i < (b); i++) #define _for(i, a, b) for(register int i = (a); i <= b; i++) using namespace std; const int MAXN = 600 + 10; int dir[4][2] = {0, 1, 0, -1, 1, 0, -1, 0}; int gas[MAXN * MAXN], f[MAXN*MAXN], n; char a[MAXN][MAXN], s[MAXN][MAXN]; int find(int x) { if(f[x] == x) return x; return f[x] = find(f[x]); } void Union(int i, int j) { int a = find(i), b = find(j); if(a != b) f[a] = b, gas[b] += gas[a]; } void put(int x, int y, char ch) { s[x][y] = ch; REP(i, 0, 4) { int xx = x + dir[i][0], yy = y + dir[i][1]; if(s[xx][yy] == '#') continue; else if(s[xx][yy] == '.') gas[find(ID(x, y))]++; else gas[find(ID(xx, yy))]--; } REP(i, 0, 4) { int xx = x + dir[i][0], yy = y + dir[i][1]; if(s[xx][yy] == ch) Union(ID(x, y), ID(xx, yy)); } } bool can_put(int x, int y, char ch) { if(s[x][y] != '.') return false; map<int, int> mp; bool ok = false; REP(i, 0, 4) { int xx = x + dir[i][0], yy = y + dir[i][1]; if(s[xx][yy] == '#') continue; else if(s[xx][yy] == '.') ok = true; else mp[find(ID(xx, yy))]++; } for(map<int, int>::iterator it = mp.begin(); it != mp.end(); it++) { int t1 = it->first, t2 = it->second; int xx = (t1 - 1) / n + 1, yy = ((t1 - 1) % n) + 1; if(s[xx][yy] == ch && gas[find(ID(xx, yy))] - t2 > 0) ok = true; if(s[xx][yy] != ch && gas[find(ID(xx, yy))] - t2 <= 0) return false; } return ok; } int main() { scanf("%d", &n); _for(i, 1, n) s[0][i] = s[n+1][i] = s[i][0] = s[i][n+1] = '#'; _for(i, 1, n) _for(j, 1, n) s[i][j] = '.'; _for(i, 1, n) scanf("%s", a[i] + 1); _for(i, 1, n) _for(j, 1, n) f[ID(i, j)] = ID(i, j); _for(i, 1, n) _for(j, 1, n) if(a[i][j] != '.') put(i, j, a[i][j]); int op = 1, x[2], y[2]; x[0] = 1, y[0] = 1, x[1] = 1, y[1] = 1; while(1) { while(x[op] <= n && y[op] <= n && !can_put(x[op], y[op], op ? 'X' : 'O')) { y[op]++; if(y[op] > n) { y[op] = 1; x[op]++; } } if(x[op] <= n && y[op] <= n) { put(x[op], y[op], op ? 'X' : 'O'); printf("%d %d\n", x[op], y[op]); } else { puts("-1 -1"); break; } op ^= 1; } return 0; }
总结
(1)考场策略。到时候noip,第一题拿下,第二题尽量拿下,780分也行,第三题先打暴力的分,然后再想正解。
(2)相信自己的直觉,大胆猜想贪心
(3)用方案数乘以权值的方法计算对整体的贡献
(4)用并查集维护联通块,想清楚,静下心,一步步写。