大三第十二周学习笔记

周三

M. Rock-Paper-Scissors Pyramid(思维)

斜着看,每次复制一遍加一个

用一个栈维护,如果当前可以打败栈顶的,就出栈,如果相同,入不入栈都可以,如果打不过也要入栈,因为它可以阻挡后面的。

最后栈底就是留下来的。

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e6 + 10;
char s[N];
int top;

int main()
{
	map<char, int> mp;
	mp['S'] = 0; mp['P'] = 1; mp['R'] = 2;

	int T; scanf("%d", &T);
	while(T--)
	{
		string str; cin >> str;
		int len = str.size();

		top = 0;
		rep(i, 0, len)
		{
			while(top && (mp[str[i]] + 1) % 3 == mp[s[top]]) top--;
			s[++top] = str[i];
		}
		printf("%c\n", s[1]);
	}
	
	return 0;
}

P3396 哈希冲突(根号分治)

用根号为分界线,一部分暴力,一部分预处理

对于这道题,模数小于等于根号n时,直接预处理

模数大于等于根号n时,直接暴力

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1.5e5 + 10;
const int M = 400;
int s[M][M], a[N], n, m;

int main()
{
	scanf("%d%d", &n, &m);
	_for(i, 1, n) scanf("%d", &a[i]);

	int block = sqrt(n);
	_for(i, 1, n)
		_for(j, 1, block)
			s[j][i % j] += a[i];
	
	while(m--)
	{
		char op[10]; int x, y;
		scanf("%s%d%d", op, &x, &y);
		if(op[0] == 'A')
		{
			if(x <= block) printf("%d\n", s[x][y]);
			else
			{
				int res = 0;
				for(int i = y; i <= n; i += x) res += a[i];
				printf("%d\n", res);
			}
		}
		else
		{
			_for(j, 1, block) s[j][x % j] += y - a[x];
			a[x] = y;
		}
	}
	
	return 0;
}

Array Queries(根号分治)

1e5左右的数据范围要很敏感,很可能是n根号n的算法

这题而言,每次p最少会增加k

那么当k大于根号n时,最多跳根号n次

当k小于根号n时,可以用dp直接求出,倒着求一遍

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e5 + 10;
const int M = 350;
int dp[N][M], a[N], n, m;

int main()
{
	scanf("%d", &n);
	_for(i, 1, n) scanf("%d", &a[i]);

	int block = sqrt(n);
	for(int i = n; i >= 1; i--)
		_for(k, 1, block)
		{
			int j = i + a[i] + k;
			if(j > n) dp[i][k] = 1;
			else dp[i][k] = dp[j][k] + 1;
		}
	
	scanf("%d", &m);
	while(m--)
	{
		int p, k;
		scanf("%d%d", &p, &k);
		if(k <= block) printf("%d\n", dp[p][k]);
		else
		{
			int res = 0;
			while(p <= n)
			{
				p = p + a[p] + k;
				res++;
			}
			printf("%d\n", res);
		}
	}
	
	return 0;
}

周四

A. Ban or Pick, What's the Trick(记忆化搜索)

注意要逆推,当前的选择肯定根据选择之后的结果是什么

逆推的话,记忆化搜索比写循环要方便

注意空间要开两倍

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 1e5 + 10;
int a[N], b[N], n, k;
ll dp[N << 1][15][15];    //交之前一定要检查一下空间开够了没有

ll dfs(int round, int numa, int numb)
{
	if(round == 2 * n + 1) return 0;
	if(dp[round][numa][numb] != -1) return dp[round][numa][numb];

	int ta = round / 2 - numb + numa;
	int tb = (round + 1) / 2 - numa + numb;

	if(round % 2 == 1)
	{
		ll res = dfs(round + 1, numa, numb);
		if(ta + 1 <= n && numa + 1 <= k) res = max(res, dfs(round + 1, numa + 1, numb) + a[ta + 1]);
		return dp[round][numa][numb] = res;
	}
	else
	{
		ll res = dfs(round + 1, numa, numb);
		if(tb + 1 <= n && numb + 1 <= k) res = min(res, dfs(round + 1, numa, numb + 1) - b[tb + 1]);
		return dp[round][numa][numb] = res;
	}
}

int main()
{
	scanf("%d%d", &n, &k);
	_for(i, 1, n) scanf("%d", &a[i]);
	_for(i, 1, n) scanf("%d", &b[i]);

	sort(a + 1, a + n + 1, greater<int>());
	sort(b + 1, b + n + 1, greater<int>());

	memset(dp, -1, sizeof dp);
	printf("%lld\n", dfs(1, 0, 0));
	
	return 0;
}

周五

G. Matryoshka Doll(dp+组合数学)

很容易想到dp[i][j]表示前i个物品分成j组的方案数

当前物品要不独立成一组,要不和之前的一组

设p为最大的下标使得ai - ap >= r

那么显然ai只能和1到p分为一组

那么关键是1到p有多少组可以让ai加进去呢

注意到p+1到i-1这一部分,两两是不可能为一组的,且ai不能和它们一组

那么只剩下j - (i - p - 1)这么多组了,ai可以加入其中一个

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 5000 + 10;
const int mod = 998244353;
int L[N], a[N], n, k, R;
ll dp[N][N];

int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		scanf("%d%d%d", &n, &k, &R);
		_for(i, 1, n) scanf("%d", &a[i]);

		int l = n;
		for(int r = n; r >= 1; r--)
		{
			while(l - 1 >= 0 && a[r] - a[l] < R) l--;
			L[r] = l;
		}

		dp[0][0] = 1;
		_for(i, 1, n)
			_for(j, 1, k)
			{
				dp[i][j] = dp[i - 1][j - 1];
				int t = i - L[i] - 1;
				if(j - t > 0) dp[i][j] = (dp[i][j] + dp[i - 1][j] * (j - t) % mod) % mod;
			}
		printf("%lld\n", dp[n][k]);
	}
	
	return 0;
}

J. Sum Plus Product(找规律)

那a,b,c三个数试一下,是abc+ac+bc+ab+a+b+c

每一项就是abc这三个取字母或者取1

可以写成(a + 1)(b + 1)(c + 1) - 1

那么答案就是加1乘起来然后减1

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int mod = 998244353;

int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		int n; scanf("%d", &n);

		ll ans = 1;
		_for(i, 1, n)
		{
			int x; scanf("%d", &x);
			ans = ans * (x + 1) % mod;
		}
		ans--;
		if(ans < 0) ans += mod;
		printf("%lld\n", ans);
	}
	
	return 0;
}

Shortest Path in GCD Graph(容斥)

找和u和v互质的数有多少个,那么将两个数质因数分解,然后容斥一下即可

主要要特判gcd=2的情况

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 1e7 + 10;
int vis[N], a[N], n, q, ans;
vector<int> p, ve;

void init()
{
	vis[0] = vis[1] = 1;
	_for(i, 2, 1e7)
	{
		if(!vis[i]) a[i] = i, p.push_back(i);  //取最小质因子的时候不要忘了质数为质数本身
		for(int x: p)
		{
			if(x * i > 1e7) break;
			vis[x * i] = 1;
			a[x * i] = x;
			if(i % x == 0) break;
		}
		
	}
}

vector<int> deal(int x)
{
	vector<int> res;
	while(x > 1)
	{
		int t = a[x];
		res.push_back(t);
		while(x % t == 0) x /= t;
	}
	return res;
}

void dfs(int i, int cnt, ll cur)
{
	if(i == ve.size())
	{
		if(cur > 1)
		{
			if(cnt % 2 == 1) ans += n / cur;
			else ans -= n / cur;
		}
		return;
	}
	dfs(i + 1, cnt + 1, cur * ve[i]);
	dfs(i + 1, cnt, cur);
}

int gcd(int a, int b) { return !b ? a: gcd(b, a % b); }

int main()
{
	init();

	scanf("%d%d", &n, &q);
	while(q--)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		vector<int> pu = deal(u), pv = deal(v);

		ve = pu;
		for(int x: pv) ve.push_back(x);
		sort(ve.begin(), ve.end());
		ve.erase(unique(ve.begin(), ve.end()), ve.end());

		if(ve.size() == pu.size() + pv.size())
		{
			puts("1 1");
			continue;
		}

		ans = 0;
		dfs(1, 1, ve[0]);
		dfs(1, 0, 1);
		printf("%d %d\n", 2, n - ans + (gcd(u, v) == 2));
 	}
	
	return 0;
}

Arithmetic Subsequence(奇偶分治)

这题妙啊,奇数偶数是突破口

奇数全部放左边,偶数全部放右边

那么三元组一定全在左边或权在右边

然后考虑递归处理,比如现在全是奇数,那么相减后最低位都为0,那么就没用了,所有数直接右移一位,这时又产生了奇数和偶数。

考虑二进制位,从最低位开始,为1的放左边为0放右边,然后再看倒数第二位……

那么就按照这个策略排序一遍,就一定不会有等差数列

特判一下一个数出现三次以上

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 5e3 + 10;
unordered_map<int, int> mp;
int a[N], n;

bool cmp(int x, int y)
{
	_for(j, 0, 30)
	{
		int tx = (x >> j) & 1, ty = (y >> j) & 1;
		if(tx != ty) return tx > ty;
	}
	return true;   //注意可能有相等的情况 要return true
}

int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		mp.clear();
		scanf("%d", &n);

		int flag = 1;
		_for(i, 1, n) 
		{
			scanf("%d", &a[i]);
			if(++mp[a[i]] >= 3) flag = 0;
		}

		if(!flag)
		{
			puts("NO");
			continue;
		}

		puts("YES");
		sort(a + 1, a + n + 1, cmp);
		_for(i, 1, n) printf("%d ", a[i]);
		puts("");
	}
	
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值