AGC003 题解
A - Wanna go back home
注意到横纵坐标是独立的,因此可以分开考虑。
考虑横坐标或纵坐标最终为零的充要条件为:
- 没有出现任何关于它的任何操作(没有
N
和S
,或没有W
和E
); - 出现所有关于它的任何操作(有向正方向走与往负方向走,如有
N
和S
,或有W
和E
)。
统计每种字母是否出现即可。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll Read() {
int sig = 1;
ll num = 0;
char c = getchar();
while(!isdigit(c)) {
if(c == '-') {
sig = -1;
}
c = getchar();
}
while(isdigit(c)) {
num = (num << 3) + (num << 1) + (c ^ 48);
c = getchar();
}
return num * sig;
}
void Write(ll x) {
if(x < 0) {
putchar('-');
x = -x;
}
if(x >= 10) {
Write(x / 10);
}
putchar((x % 10) ^ 48);
}
int main() {
string s;
cin >> s;
bool a, b, c, d;
a = b = c = d = false;
int i;
for(i = 0; i < s.size(); i++) {
if(s[i] == 'N') {
a = true;
}
if(s[i] == 'S') {
b = true;
}
if(s[i] == 'W') {
c = true;
}
if(s[i] == 'E') {
d = true;
}
}
if(!(a ^ b) && !(c ^ d)) {
printf("Yes\n");
}
else {
printf("No\n");
}
return 0;
}
B - Simplified majhong
考虑一个极大的区间
假设我们用相同点数的牌凑对子,则剩下的牌数量为满足
考虑一个有序数列
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll Read() {
int sig = 1;
ll num = 0;
char c = getchar();
while(!isdigit(c)) {
if(c == '-') {
sig = -1;
}
c = getchar();
}
while(isdigit(c)) {
num = (num << 3) + (num << 1) + (c ^ 48);
c = getchar();
}
return num * sig;
}
void Write(ll x) {
if(x < 0) {
putchar('-');
x = -x;
}
if(x >= 10) {
Write(x / 10);
}
putchar((x % 10) ^ 48);
}
const int N = 100005;
int n, a[N];
int main() {
int i;
n = Read();
for(i = 1; i <= n; i++) {
a[i] = Read();
}
ll sum = 0, ans = 0;
for(i = 1; i <= n + 1; i++) {
if(a[i]) {
sum += a[i];
}
else {
ans += sum / 2, sum = 0;
}
}
Write(ans), putchar('\n');
return 0;
}
C - BBuBBBlesort!
考虑第二种操作等价于交换下标相差
考虑目标序列,注意到每个数互不相同,因此会出现在原序列中某个数下标的奇偶性与目标序列这个数下标的奇偶性不同(下文我们称一个数是不合法的,当且仅当它满足上述条件,反之这个数就是合法的)。
不合法的数永远也无法仅通过第二种操作交换到目标位置,这个时候就需要第一种操作,这个操作可以改变两个数下标的奇偶性。
我们先可以用第二种操作,让两个不合法的数相邻,在使用第一种操作,让两个不合法的数交换,从而变成合法的数。
因此若不合法的数有
可以证明不合法的数有
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll Read() {
int sig = 1;
ll num = 0;
char c = getchar();
while(!isdigit(c)) {
if(c == '-') {
sig = -1;
}
c = getchar();
}
while(isdigit(c)) {
num = (num << 3) + (num << 1) + (c ^ 48);
c = getchar();
}
return num * sig;
}
void Write(ll x) {
if(x < 0) {
putchar('-');
x = -x;
}
if(x >= 10) {
Write(x / 10);
}
putchar((x % 10) ^ 48);
}
const int N = 100005;
int n;
struct tNode {
int val, id;
friend bool operator <(tNode x, tNode y) {
return x.val < y.val;
}
}a[N];
int main() {
int i;
n = Read();
for(i = 1; i <= n; i++) {
a[i].val = Read(), a[i].id = i;
}
sort(a + 1, a + n + 1);
int ans = 0;
for(i = 1; i <= n; i++) {
if((a[i].id & 1) ^ (i & 1)) {
ans++;
}
}
Write(ans / 2), putchar('\n');
return 0;
}
D - Anticube
记
首先,注意到如果一个数有一个因子是立方数,那么这个因子将对答案没有影响,可以直接删去。
现在我们对
现在我们需要找到一个满足上述条件的数
可以发现:
; 当且仅当 ;- 如果存在
满足 ,则 为完全立方数。
假设我们求出了每个 map
即可做到
现在问题转化为如何求
考虑优化,注意到我们在删去立方数因子的时候,复杂度是
因此我们对质因子
- 当
时,可以在删去立方数因子的同时顺便处理掉。 - 否则注意到这样的
不会超过两个,将 的质因子处理掉后,剩下的 (记其为 )还可能是 三种形式( 均表示质因数,且 )。- 对于
的情况,那么它不能表示成两个质数相乘的形式,因为这样的话存在一个质数不大于 ,这是不大于 的 - 对于
的情况:- 若有
,那么 至少会被乘上 ,这是大于 的,即 一定不在 中出现过,打标并直接累加到答案。 - 否则
, 会被乘上 。 - 综上,判断
是否为完全平方数,若是, ,否则打标直接统计到答案内。
- 若有
- 对于
代码里可能要用到 __int128
(其实是因为我懒)。
注意对
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll Read() {
int sig = 1;
ll num = 0;
char c = getchar();
while(!isdigit(c)) {
if(c == '-') {
sig = -1;
}
c = getchar();
}
while(isdigit(c)) {
num = (num << 3) + (num << 1) + (c ^ 48);
c = getchar();
}
return num * sig;
}
void Write(ll x) {
if(x < 0) {
putchar('-');
x = -x;
}
if(x >= 10) {
Write(x / 10);
}
putchar((x % 10) ^ 48);
}
const int N = 100005, M = 2205;
int n, cnt;
ll a[N], b[N], prime[M];
bool st[M];
void Get_Prime(int n) {
int i, j;
for(i = 2; i <= n; i++) {
if(!st[i]) {
prime[++cnt] = i;
}
for(j = 1; j <= cnt && prime[j] * i <= n; j++) {
st[prime[j] * i] = true;
if(i % prime[j] == 0) {
break;
}
}
}
}
int main() {
Get_Prime(M - 5);
int i, j;
n = Read();
for(i = 1; i <= n; i++) {
a[i] = Read();
ll x = a[i], ca = 1;
__int128 cb = 1;
for(j = 1; j <= cnt; j++) {
ll cp = prime[j] * prime[j] * prime[j];
if(x % prime[j] == 0) {
int c = 0;
while(x % prime[j] == 0) {
x /= prime[j], c++;
if(c == 3) {
a[i] /= cp, c = 0;
}
}
if(c == 1) {
ca *= prime[j], cb *= prime[j] * prime[j];
}
if(c == 2) {
cb *= prime[j], ca *= prime[j] * prime[j];
}
}
}
ca *= x;
if(x <= 100000ll) {
cb *= x * x;
}
else {
ll sq = (ll)sqrt(x);
if(sq * sq == x) {
cb *= sq;
}
else {
cb = -1;
}
}
a[i] = ca;
if(cb > (ll)(1e10)) {
b[i] = -1;
}
else {
b[i] = cb;
}
}
map<ll, int> mp;
int ans = 0, cnt = 0;
for(i = 1; i <= n; i++) {
if(b[i] == -1) {
ans++;
}
else if(a[i] == 1) {
cnt++;
}
else {
mp[a[i]]++;
}
}
for(i = 1; i <= n; i++) {
if(b[i] != -1 && a[i] != 1) {
ans += max(mp[a[i]], mp[b[i]]);
mp[a[i]] = mp[b[i]] = 0;
}
}
Write(ans + (cnt > 0));
return 0;
}
E - Sequential operations on Sequence
以下简记
首先,假设有
考虑这个东西该怎么统计,我们可以将
在接着讲之前,我们先证明:最终的序列一定可以拆成若干个连续子序列
证明:考虑数学归纳法,先将操作变成一个上升的操作序列。
当只有一个操作时,命题显然成立。
假设第个操作后,序列满足命题,那么 会重复若干次 ,再接上一个序列的前缀,这时命题成立还是较为显然的。
看似这个东西很没用,但这启发我们可以将
例如操作序列为
标红的是
看
标红的是
我们标记
标红和标绿的是
最后,出现了
可以直接统计答案,注意有三倍的贡献。
综上,我们的代码流程如下:
- 用单调栈维护一个上升的操作序列,记操作序列长度为
; - 倒序处理
的出现次数,记为 ,初始有 ; - 每次对
,令当前序列长度为 ,初始化 ,有如下操作:- 二分出第一个不大于
的操作参数 ; - 算出
在 中的出现次数,及后面的零散小序列, , 。 - 重复执行以上两个操作,直到
,这时直接将 的数的答案加上 (因为 出现了 次)。
- 二分出第一个不大于
- 最后对
进行统计,将 的数的答案加上 。
暴力加会超时,可以用差分数组,这样递归与二分均是
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll Read() {
int sig = 1;
ll num = 0;
char c = getchar();
while(!isdigit(c)) {
if(c == '-') {
sig = -1;
}
c = getchar();
}
while(isdigit(c)) {
num = (num << 3) + (num << 1) + (c ^ 48);
c = getchar();
}
return num * sig;
}
void Write(ll x) {
if(x < 0) {
putchar('-');
x = -x;
}
if(x >= 10) {
Write(x / 10);
}
putchar((x % 10) ^ 48);
}
const int N = 100005;
int n, m;
ll a[N], cnt[N], d[N];
void Add(ll p, ll w) {
int pos = lower_bound(a + 1, a + m + 1, p) - a - 1;
if(!pos) {
d[1] += w, d[p + 1] -= w;
}
else {
cnt[pos] += w * (p / a[pos]), Add(p % a[pos], w);
}
}
int main() {
int q, i;
n = Read(), q = Read();
a[++m] = n;
for(i = 1; i <= q; i++) {
ll x = Read();
while(a[m] >= x && m) {
a[m--] = 0;
}
a[++m] = x;
}
cnt[m] = 1;
for(i = m - 1; i; i--) {
Add(a[i + 1], cnt[i + 1]);
}
d[1] += cnt[1], d[a[1] + 1] -= cnt[1];
for(i = 1; i <= n; i++) {
d[i] += d[i - 1];
Write(d[i]), putchar('\n');
}
return 0;
}
F - Fraction of Fractal
注意到黑格四连通这个性质,因此容易发现最后的连通块数量与以下两个条件有关。
- 将两个图形上下相接,若所有黑格连通,我们称这个图形具有性质一;
- 将两个图形左右相接,若所有黑格连通,我们称这个图形具有性质二。
首先我们有:
- 若一个图形同时满足性质一和性质二,那么连通块个数为
; - 若一个图形不满足性质一和性质二,设黑格个数为
,那么连通块个数为 ; - 否则容易发现仅满足性质二可以通过把图形旋转
使图形满足性质一。
我们考虑图形满足性质一的情况。
在此之前,注意到我们可以把定义修改为(原始文本来自洛谷):
我们定义「
级分形」如下: 级分形是一个 的黑色单元格。 级分形由 级分形变化而来。具体地,将原图形的每一个黑色单元格替换为 级分形,每一个白色单元格替换为与 Snuke 的网格尺寸相同的全部为白色的网格,就成了 级分形。
我们先假设原先所有的连通块有
那么
整理一下:
其中
初始矩阵:
其中
转移矩阵:
即:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll Read() {
int sig = 1;
ll num = 0;
char c = getchar();
while(!isdigit(c)) {
if(c == '-') {
sig = -1;
}
c = getchar();
}
while(isdigit(c)) {
num = (num << 3) + (num << 1) + (c ^ 48);
c = getchar();
}
return num * sig;
}
void Write(ll x) {
if(x < 0) {
putchar('-');
x = -x;
}
if(x >= 10) {
Write(x / 10);
}
putchar((x % 10) ^ 48);
}
const ll Mod = 1e9 + 7;
const int K = 15;
struct Matrix {
ll n, m, a[K][K];
friend Matrix operator *(Matrix a, Matrix b) {
int i, j, k;
Matrix r;
memset(r.a, 0, sizeof(r.a)), r.n = a.n, r.m = b.m;
for(i = 0; i < r.n; i++) {
for(j = 0; j < a.m; j++) {
for(k = 0; k < r.m; k++) {
r.a[i][k] = (r.a[i][k] + a.a[i][j] * b.a[j][k] % Mod) % Mod;
}
}
}
return r;
}
}dm, bm;
Matrix QuickPow(Matrix x, ll y) {
if(y == 1) {
return x;
}
Matrix half = QuickPow(x, y >> 1);
if(y & 1) {
return half * half * x;
}
return half * half;
}
const int N = 1005;
int n, m;
bool grid[N][N];
ll k, a, b, c;
ll QuickPow(ll x, ll y) {
if(y == 0) {
return 1;
}
if(y == 1) {
return x;
}
ll half = QuickPow(x, y >> 1);
if(y & 1) {
return half * half % Mod * x % Mod;
}
return half * half % Mod;
}
int main() {
int i, j;
cin >> n >> m >> k;
for(i = 1; i <= n; i++) {
string s;
cin >> s;
for(j = 1; j <= m; j++) {
grid[i][j] = (s[j - 1] == '#');
}
}
if(k == 0 || k == 1) {
printf("1\n");
return 0;
}
bool ch = false, cc = false;
for(i = 1; i <= n; i++) {
if(grid[i][1] && grid[i][m]) {
ch = true;
}
}
for(i = 1; i <= m; i++) {
if(grid[1][i] && grid[n][i]) {
cc = true;
}
}
for(i = 1; i <= n; i++) {
for(j = 1; j <= m; j++) {
a += grid[i][j];
}
}
if(!ch && !cc) {
Write(QuickPow(a, k - 1)), putchar('\n');
return 0;
}
else if(ch && cc) {
printf("1\n");
return 0;
}
if(ch) {
for(i = 1; i <= n; i++) {
for(j = 1; j < m; j++) {
b += (grid[i][j] && grid[i][j + 1]);
}
}
for(i = 1; i <= n; i++) {
c += (grid[i][1] && grid[i][m]);
}
}
else {
for(i = 1; i < n; i++) {
for(j = 1; j <= m; j++) {
b += (grid[i][j] && grid[i + 1][j]);
}
}
for(i = 1; i <= m; i++) {
c += (grid[1][i] && grid[n][i]);
}
}
bm.n = 1, bm.m = 2, bm.a[0][0] = 1, bm.a[0][1] = 1;
dm.n = dm.m = 2, dm.a[0][0] = a, dm.a[0][1] = 0, dm.a[1][0] = (Mod - b) % Mod, dm.a[1][1] = c;
Matrix res = bm * QuickPow(dm, k - 1);
Write(res.a[0][0]), putchar('\n');
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】