比赛完了,感想就是题目质量出得真得是很一般,很一般,原因应该是没有验题叭。导致第二题题目意思理解很模糊,连续的顺子歧义太大,第五题考试的时候没有看懂,X进制和十进制之间的转换又是什么呢?哎,希望能拿到省一叭艹。
今天在acwing上面交了一下搭积木,递推公式一步写错了,结果直接寄了,绝了,不说了,太傻逼了(第一次公开场合骂人额鹅鹅鹅)
考试的时候,不停地给我跳黄牌,心烦意乱的,状态确实没有发挥到最好,可惜了。
申明:本题解在ACWing上已经AC
这道题就是一个模拟就可以了,答案是1478
#include
#include
#include
using namespace std;
int main()
{
int n = 2022;
int res = 0, t = 1;
while (n)
{
res += t * (n % 10);
t *= 9;
n /= 10;
}
cout << res << endl;
return 0;
}
这题就很奇怪了,考试的时候没怎么仔细想,之后才发现,问题很大,组委会必须背锅。0算不算连续的数字呢?
考试的时候我是没有仔细去想,直接编程写的太麻烦惹,算出来是14
,一月份20~29总共是十天,十二月30和31日两天,10月12日,11月23日,总共14天
简单的模拟,这道题就是先把日期给除到每周,看还剩下几天,分成前5天和后两天比较,时间复杂度O(1)
#include
#include
#include
using namespace std;
const int N = 100010;
typedef long long ll;
ll a, b, n;
int main()
{
cin >> a >> b >> n;
ll res = 0;
ll cnt = n / (5 * a + 2 * b);
res += cnt * 7;
n %= 5 * a + 2 * b;
if (n <= 5 * a)
{
res += n / a;
n %= a;
if (n) res ++;
}
else
{
res += 5;
n -= 5 * a;
if (n <= b) res ++ ;
else res += 2;
}
cout << res << endl;
return 0;
}
这道题是一道思维题,就是说灌木在第一次被剪到下一次被剪的时间段应该是长得最高,同时两个方向取最大值即可,时间复杂度:O(n)
#include
#include
#include
using namespace std;
const int N = 10010;
typedef long long ll;
int n;
int a[N];
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ )
{
int t = max(2 * (i - 1), 2 * (n - i));
printf("%d\n", t);
}
return 0;
}
这道题也是一道思维题。两个数尽量差最小,不就是每一位进制尽可能小嘛,这题进制好像都无所谓,那每一位最小进制是多少呢,比较适合连个数中最大的数加1嘛。
考试的时候,不知道X进制跟10进制怎么转换的,想了一会,寄了
#include
#include
#include
using namespace std;
const int N = 100010, MOD = 1000000007;
typedef long long ll;
int n, m1, m2;
int a[N], b[N], c[N];
int main()
{
cin >> n;
cin >> m1;
for (int i = m1 - 1; i >= 0; i -- ) scanf("%d", &a[i]);
cin >> m2;
for (int i = m2 - 1; i >= 0; i -- ) scanf("%d", &b[i]);
ll res = 0, m = max(m1, m2);
for (int i = m - 1; i >= 0; i -- )
{
c[i] = max(2, max(a[i], b[i]) + 1);
}
for (int i = m - 1; i >= 0; i -- )
{
if (i)
res = (res + (ll)(a[i] - b[i])) * c[i - 1] % MOD;
else
res = (res + a[i] - b[i]) % MOD;
}
cout << res << endl;
return 0;
}
这道题用到算法了,是前缀和+双指针,如果学过的话比较好理解的,当然暴力应该是可以做到60%的数据,不过这道题不是新出的题目惹
#include
#include
#include
using namespace std;
typedef long long LL;
const int N = 510;
int n, m, T;
int s[N][N];
int main()
{
scanf("%d%d%d", &n, &m, &T);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
{
scanf("%d", &s[i][j]);
s[i][j] += s[i - 1][j];
}
LL res = 0;
for (int i = 1; i <= n; i ++ )
for (int j = i; j <= n; j ++ )
for (int l = 1, r = 1, sum = 0; r <= m; r ++ )
{
sum += s[j][r] - s[i - 1][r];
while (sum > T)
{
sum -= s[j][l] - s[i - 1][l];
l ++ ;
}
res += r - l + 1;
}
printf("%lld\n", res);
return 0;
}
这道题是一道递推。我的想法可能比较奇怪,就是考虑最后一列状态作为分界点:分为最后一列摆0个,只摆上面一行,只摆下面一行和最后一列都摆。考试的时候也只想到了这个方法,因为这道题行数只有2维,不需要状态压缩。
递推公式该怎么考虑呢?
我是用0表示这一列不摆,1表示这一列摆满,2表示只摆上面,3表示只摆下面。
首先是这一列不摆,那上一列一定要摆满,否则会有空隙f[i][0]=f[i-1][1]
,接着是摆满,摆满可以上一列刚好摆满f[i-1][1]
,也可以上一列不摆,摆两个横的f[i-1][0]
,也可以是上一列摆第一行,这一列摆一个三角形,同理对称一下f[i-1][2]+f[i-1][3]
,综上就是f[i][1]=f[i-1][0]+f[i-1][1]+f[i-1][2]+f[i-1][3]
。接着是这一列只摆上面一行,那么可以是什么呢?这一列上面一行可以是一个横着的12积木,也可以是一个三角形凸出来,如果摆12的积木,那么前一列必须把下面补齐也就是f[i-1][3]
表示上一列只摆下面一行。最后就是这一列只摆下面一行,同这一列摆第一行。
初始化的时候,因为我这个状态比较特殊,所以第一列我直接自己赋值了,因为第一列不能只摆一行,所以循环最好从第二列开始。
综上,就是大概四个状态递推就可以了。因为数据范围比较大,所以需要滚动数组,否则一个样例可能过不掉,同时需要记得取mod。
#include
#include
#include
#include
#include
using namespace std;
const int N = 10000000 + 10, MOD = 1000000007;
typedef long long ll;
ll f[2][4];
int main()
{
int n;
cin >> n;
f[1 & 1][0] = 1;
f[1 & 1][1] = 1;
for (int i = 2; i <= n; i ++ )
{
f[i & 1][0] = f[(i - 1) & 1][1];
f[i & 1][1] = (f[(i - 1) & 1][1] + f[(i - 1) & 1][2] + f[(i - 1) & 1][3] + f[(i - 1) & 1][0]) % MOD;
f[i & 1][2] = (f[(i - 1) & 1][3] + f[(i - 1) & 1][0]) % MOD;
f[i & 1][3] = (f[(i - 1) & 1][2] + f[(i - 1) & 1][0]) % MOD;
}
cout << f[n & 1][1] << endl;
return 0;
}
这道题终于会了,感觉是全场最难的一道题,因为需要手写哈希。原来的考点是图的遍历,怎么搞都可以,最难的在于哈希映射,一般采用开放寻址法和拉链法。如果使用字符串映射的话会TLE,那么这个时候只能选择整数映射,把坐标映射成 ( x ∗ 1 e 9 + 7 ) + y ) (x*1e9+7)+y) (x∗1e9+7)+y)这种形式
// shiran
#include
using namespace std;
#define rep(i, a, n) for (int i = a; i < n; i++)
#define per(i, n, a) for (int i = n - 1; i >= a; i--)
#define sz(x) (int)size(x)
#define fi first
#define se second
#define all(x) x.begin(), x.end()
#define pb push_back
#define mk make_mair
typedef long long ll;
typedef pair<int, int> PII;
const int mod = 1e9+7;
const int N = 50010, M = 999997;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
int n, m;
struct Circle
{
int x, y, r;
}p[N];
ll h[M], id[M];
bool st[M];
int sqr(int x)
{
return x * x;
}
ll get_key(int x, int y)
{
return (ll)x * INT_MAX + y;
}
int find(int x, int y)
{
ll key = get_key(x, y);
int t = (key % M + M) % M;
while (h[t] != -1 && h[t] != key)
if ( ++ t == M)
t = 0;
return t;
}
void dfs(int x, int y, int r)
{
st[find(x, y)] = true;
for (int i = x - r; i <= x + r; i ++ )
for (int j = y - r; j <= y + r; j ++ )
{
if (sqr(i - x) + sqr(j - y) <= sqr(r))
{
int t = find(i, j);
if (id[t] && !st[t])
dfs(i, j, p[id[t]].r);
}
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
memset(h, -1, sizeof h);
rep(i, 1, n + 1)
{
int x, y, r;
cin >> x >> y >> r;
p[i] = {x, y, r};
int t = find(x, y);
if (h[t] == -1) h[t] = get_key(x, y);
if (!id[t] || p[id[t]].r < r)
id[t] = i; // 只存最大的半径
}
while (m -- )
{
int x, y, r;
cin >> x >> y >> r;
for (int i = x - r; i <= x + r; i ++ )
for (int j = y - r; j <= y + r; j ++ )
{
if (sqr(i - x) + sqr(j - y) <= sqr(r))
{
int t = find(i, j);
if (id[t] && !st[t])
dfs(i, j, p[id[t]].r);
}
}
}
int res = 0;
rep(i, 1, n + 1)
if (st[find(p[i].x, p[i].y)])
res ++ ;
cout << res << endl;
return 0;
}
这道题也是一道DP,考试的时候太笨了,没有反过来去思考,赛后自己也直接写出来了,哎,还是自己太菜了。这道题状态很好想f[i][j][k]
表示走过了i
个店,遇见了j
朵花,最后还有k
壶酒的合法方案数,注意必须是合法的。
合法是什么意思呢?就是说现在的壶数量必须小于等于剩余花的数量。递推公式也比较简单,考虑最后一步不同的状态只有两种,就是最后碰到花还是店。如果说这次碰到了店,那么上一个店的时候只有这次酒壶的一半f[i][j][k]=f[i-1][j][k/2]
。如果说这次遇到了花,那么上一次酒数量应该比这一次多一壶f[i][j][k]=f[i][j-1][k+1]
。这道题边界也要考虑,就是说首先i
和j
都需要合法,不能是负数,其次,得从0开始循环,也就是说,经过了0家店可以碰到花,同时遇花之前也可以去店里加酒,最后取模,还有就是最后的答案,不能花和店都用完了,因为最后一次必须是遇花,那么遇花为了合法所以必须还有酒,所以答案应该是f[n][m-1][1]
,OK,那么到这里这道题就已经很完美了,虽然代码很短,我刚考完就想到了呜呜呜。
#include
#include
#include
using namespace std;
const int N = 110, MOD = 1000000007;
int n, m;
int f[N][N][N];
int main()
{
cin >> n >> m;
f[0][0][2] = 1;
for (int i = 0; i <= n; i ++ )
for (int j = 0; j <= m; j ++ )
for (int k = 0; k <= m - j; k ++ )
{
int &v = f[i][j][k];
if (i && k % 2 == 0)
v = (v + f[i - 1][j][k / 2]) % MOD;
if (j) v = (v + f[i][j - 1][k + 1]) % MOD;
}
cout << f[n][m - 1][1] << endl;
return 0;
}
其实这个题本质是一个最长公共下降子序列的问题,对于任意一个h[i]
,只要它高度降到了与前一个高度下降过程中的公共值,那么它就不需要花费代价继续下降,同时可以观察到高度下降的速度特别快(魔法就是厉害)。如果它降得的当前高度与前一个高度没有公共值,则需要多花费一个代价,来降低自己的高度。因此,可以用数组存储下降的花费和每次降低过程的值。时间复杂度为:O(n)
,因为每个数下降最多只会降低6次(我试过极限数据hhh)
#include
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
LL a[N];
vector<LL> b[N];
int n;
LL solve(LL x)
{
return sqrt(x / 2 + 1);
}
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i ++ ) scanf("%lld", &a[i]);
int res = 0;
for(int i = 1; i <= n; i ++ ) {
while(a[i] > 1) {
int flag = 0;
for(LL j : b[i - 1]) {
if(a[i] == j) {
flag = 1;
break;
}
}
if(!flag) res ++;
b[i].push_back(a[i]);
a[i] = solve(a[i]);
}
}
printf("%d", res);
return 0;
}
OK,非常感谢你能看到这里,最后我还是想再骂一下今年的线上比赛,真得是太蠢了,如果没拿省一我会再写一篇文章专门吐槽一下,请问大家,60分有机会省一嘛?哎——没机会去专心考研了,放弃算法竞赛了。