AGC003 题解

A - Wanna go back home

注意到横纵坐标是独立的,因此可以分开考虑。
考虑横坐标或纵坐标最终为零的充要条件为:

  • 没有出现任何关于它的任何操作(没有 NS,或没有 WE);
  • 出现所有关于它的任何操作(有向正方向走与往负方向走,如有 NS,或有 WE)。

统计每种字母是否出现即可。

#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

考虑一个极大的区间 [l,r],满足 i[l,r]Ai>0
假设我们用相同点数的牌凑对子,则剩下的牌数量为满足 Ai 为奇数的 i 的数目。
考虑一个有序数列 [p,q]p<q),满足 i(p,q)Aimod2=0,且 Apmod2=Aqmod2=1,因为 i(p,q)Aimod2=0Ai>0,所以 Ai2,可以将 (p,q) 间点数的对子各拆一个,然后让 (p,p+1),(p+1,p+2),,(q1,q) 各凑一个对子,这样答案每次可以增加 2,最多只剩下一张牌,这样就达到了这一极大区间对答案贡献的上界。

#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!

考虑第二种操作等价于交换下标相差 2 的两个数的位置,因此可以将整个序列按照下标的奇偶分成两个序列,显然第二种操作不能改变某个数下标的奇偶性。
考虑目标序列,注意到每个数互不相同,因此会出现在原序列中某个数下标的奇偶性与目标序列这个数下标的奇偶性不同(下文我们称一个数是不合法的,当且仅当它满足上述条件,反之这个数就是合法的)。
不合法的数永远也无法仅通过第二种操作交换到目标位置,这个时候就需要第一种操作,这个操作可以改变两个数下标的奇偶性。
我们先可以用第二种操作,让两个不合法的数相邻,在使用第一种操作,让两个不合法的数交换,从而变成合法的数。
因此若不合法的数有 x 个,则答案为 x2
可以证明不合法的数有 0 个时,一定可以仅通过第二种操作,使原序列排序。

#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

V=1010
首先,注意到如果一个数有一个因子是立方数,那么这个因子将对答案没有影响,可以直接删去。
现在我们对 si 进行处理,得到了一个数 ai,它的每个质因子的出现次数最多为 2
现在我们需要找到一个满足上述条件的数 bi,使得 ai×bi 是立方数,可以证明 bi 是唯一的(构造方法为,初始 bi=1,对所有的质数 p 满足 pai,若 p2ai,那么 bibip2,否则 bibip),我们记按上述方法令 ai=x 构造出的 bi=f(x)
可以发现:

  • f(f(x))=x
  • f(x)=x 当且仅当 x=1
  • 如果存在 i,j 满足 aj=bi,则 si×sj 为完全立方数。

假设我们求出了每个 aibi,那么我们可以考虑所有存在的 (x,f(x))xf(x) 只能选一个数加入最终的集合,可以贪心的加入出现次数多的数,用 map 即可做到 O(NlogN)
现在问题转化为如何求 bi,如果暴力分解质因数处理,时间复杂度是 O(V) 的,不能接受。
考虑优化,注意到我们在删去立方数因子的时候,复杂度是 O(V3) 的,
因此我们对质因子 p 进行分类讨论:

  • pV3 时,可以在删去立方数因子的同时顺便处理掉。
  • 否则注意到这样的 p 不会超过两个,将 pV3 的质因子处理掉后,剩下的 si(记其为 si)还可能是 p,p1p2,p2 三种形式(p,p1,p2 均表示质因数,且 p1p2)。
    • 对于 siV 的情况,那么它不能表示成两个质数相乘的形式,因为这样的话存在一个质数不大于 V4,这是不大于 V3
    • 对于 si>V 的情况:
      • 若有 si=p1p2,那么 bi 至少会被乘上 p12p22=si2,这是大于 V 的,即 bi 一定不在 ai 中出现过,打标并直接累加到答案。
      • 否则 si=p2bi 会被乘上 p
      • 综上,判断 si 是否为完全平方数,若是,bibip,否则打标直接统计到答案内。

代码里可能要用到 __int128其实是因为我懒)。
注意对 ai=1 的情况分类讨论。

#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

以下简记 pi 为第 i+1 个操作后产生的序列。
首先,假设有 qiqi1,那么 qi1 这个操作就是没用的,因此我们只要用单调栈维护一个上升的操作序列就可以了。
考虑这个东西该怎么统计,我们可以将 N 插到操作序列的开头,并将第一次操作的定义改为,对于 x,生成一个长度为 x 的序列 {1,2,,x}
在接着讲之前,我们先证明:最终的序列一定可以拆成若干个连续子序列 b1,b2,,bk,满足 bi={1,2,,|bi|}

证明:考虑数学归纳法,先将操作变成一个上升的操作序列。
当只有一个操作时,命题显然成立。
假设第 i 个操作后,序列满足命题,那么 pi+1 会重复若干次 pi,再接上一个序列的前缀,这时命题成立还是较为显然的。

看似这个东西很没用,但这启发我们可以将 pi 的贡献拆成 pjji)的贡献加上某些零散小序列的贡献。
例如操作序列为 {3,7,11,13} 时,我们要计算 p4 的答案。
p4 如下:

{1,2,3,1,2,3,1,1,2,3,1,1,2}

标红的是 p3。还剩一个长度为 2 的序列,因为 2<q1,所以它无法包含任何一个 pi,因此它是 {1,2,,x} 的形式(它是一个形如 {1,2,,x} 序列的前缀,他一定也是形如 {1,2,,x} 的),直接统计到答案内,p3 的出现次数加 1
p3

{1,2,3,1,2,3,1,1,2,3,1}

标红的是 p2。还剩一个长度为 4 的序列,因为 4q1,所以它包含至少一个 p1(标绿的部分),但是 4<q2,所以它不包含 p2
我们标记 p1 的出现次数加 11=4q1),剩一个长度为 1 的序列,因为 1<q1 所以仿照上文统计答案,最后 p2 的出现次数加 1
p2 为:

{1,2,3,1,2,3,1}

标红和标绿的是 p1,注意到 p1 出现了 2 次(2=7q1),将 p1 的出现次数加 2,剩下一个长度为 1 的序列处理方式同上。
最后,出现了 3 次的 p1 为:

{1,2,3}

可以直接统计答案,注意有三倍的贡献。
综上,我们的代码流程如下:

  • 用单调栈维护一个上升的操作序列,记操作序列长度为 Q
  • 倒序处理 pi 的出现次数,记为 fi,初始有 fQ=1
  • 每次对 2iQ,令当前序列长度为 x,初始化 x=qi,有如下操作:
    • 二分出第一个不大于 x 的操作参数 qj
    • 算出 pjpi 中的出现次数,及后面的零散小序列,fjfj+xqjxxmodqj
    • 重复执行以上两个操作,直到 x<q1,这时直接将 1x 的数的答案加上 fi(因为 pi 出现了 fi 次)。
  • 最后对 f1 进行统计,将 1q1 的数的答案加上 f1

暴力加会超时,可以用差分数组,这样递归与二分均是 O(logN) 的,总复杂度 O(Nlog2N)

#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

注意到黑格四连通这个性质,因此容易发现最后的连通块数量与以下两个条件有关。

  • 将两个图形上下相接,若所有黑格连通,我们称这个图形具有性质一;
  • 将两个图形左右相接,若所有黑格连通,我们称这个图形具有性质二。

首先我们有:

  • 若一个图形同时满足性质一和性质二,那么连通块个数为 1
  • 若一个图形不满足性质一和性质二,设黑格个数为 a,那么连通块个数为 aK1
  • 否则容易发现仅满足性质二可以通过把图形旋转 90 使图形满足性质一。

我们考虑图形满足性质一的情况。
在此之前,注意到我们可以把定义修改为(原始文本来自洛谷):

我们定义「n 级分形」如下:0 级分形是一个 1×1 的黑色单元格。n 级分形由 n1 级分形变化而来。具体地,将原图形的每一个黑色单元格替换为 n1 级分形,每一个白色单元格替换为与 Snuke 的网格尺寸相同的全部为白色的网格,就成了 n 级分形。

image

我们先假设原先所有的连通块有 x 个且它们互相不连通,令原图形的黑格个数为 a,则连通块有 xa 个,我们再减去上下连通的贡献,即原图形内部黑块上下连通的组数 b,乘上上一分形中,连通块的组数 y,满足两个连通块使得可以通过上下拼接形成一个连通块。综上,连通块个数 x=xayb,即为 x=1×51×2=3
那么 y 又是如何转移的呢?注意到因为图形仅满足性质一,所以 y=yc,其中 c 为图形中可以通过上下拼接连通的黑块的个数,即 y=1×2=2
整理一下:

{xN=xN1ayN1b.yN=yN1c.

其中 a,b,c 为常数,写个矩阵快速幂即可。
初始矩阵:

[x1y1]

其中 x1=y1=1
转移矩阵:

[a0bc]

即:

[x1y1]×[a0bc]K1=[xKyK]

#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;
}
posted @   Include_Z_F_R_qwq  阅读(8)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示