大三第十周学习笔记

本文深入探讨了多种算法竞赛中的核心技巧,包括字符串处理、排序、拓扑排序、区间覆盖等,通过具体实例展示了如何巧妙地解决问题。文章还介绍了如何使用数据结构如双端队列和哈希表来优化解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

周三

最近作业开始变多了,前几天也是在写作业……又要训练又要写很多作业,希望能熬过11月……

A. Lily

签到,能放就放,即两侧和本身没有L的替换成C

#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 = 1e3 + 10;
char s[N];
int n;

int main()
{
	scanf("%d%s", &n, s + 1);

	_for(i, 1, n)
	{
		if(s[i - 1] != 'L' && s[i] != 'L' && s[i + 1] != 'L') putchar('C');
		else putchar(s[i]);
	}
	puts("");
 
	return 0; 
} 

M. Youth Finale(思维)

冒泡排序需要swap的就是逆序对的个数

如果翻转,那么就用总数减去当前逆序对

如果把第一个数放到最后去,发现逆序对会改变n-2x+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 N = 3e5 + 10;
int a[N], f[N], n, m;

int lowbit(int x) { return x & -x; }

int add(int x)
{
	for(; x <= n; x += lowbit(x))
		f[x]++;
}

int sum(int x)
{
	int res = 0;
	for(; x; x -= lowbit(x))
		res += f[x];
	return res;
}

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

	ll ans = 0;
	_for(i, 1, n)
	{
		ans += sum(n) - sum(a[i]);
		add(a[i]);
	}

	printf("%lld\n", ans);
	deque<int> q;
	int op = 1;
	_for(i, 1, n) q.push_back(a[i]);

	string s; cin >> s;
	rep(i, 0, m)
	{
		if(s[i] == 'R')
		{
			ans = 1LL * n * (n - 1) / 2 - ans;
			op ^= 1;
			printf("%lld", ans % 10);
		}
		else
		{
			if(op)
			{
				int x = q.front(); q.pop_front();
				ans += n - 2 * x + 1;
				printf("%lld", ans % 10);
				q.push_back(x);
			}
			else
			{
				int x = q.back(); q.pop_back();
				ans += n - 2 * x + 1;
				printf("%lld", ans % 10);
				q.push_front(x);
			}
		}
	}
	puts("");
 
	return 0; 
} 

E. Draw a triangle(叉乘+exgcd)

这题用到了叉乘的面积公式,两个向量叉乘的绝对值就是平行四边形的面积,叉乘可以用行列式来记。

那么AB向量,设为(a, b),AC设为(x, y)

那么就是(x, y) x (a, b) = |bx - ay|

要让绝对值里面最小,那么就是exgcd可以取得最小值gcd(a, b)

用exgcd取得任意一组解即可。特判一下a=0和b=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;

typedef long long ll;

void exgcd(ll a, ll b, ll& d, ll& x, ll& y)
{
	if(!b) { d = a; x = 1; y = 0; }
	else { exgcd(b, a % b, d, y, x); y -= x * (a / b) ; }
}

int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		ll x1, y1, x2, y2;
		scanf("%lld%lld%lld%lld", &x1, &y1, &x2, &y2);

		ll a = x1 - x2, b = y1 - y2;
		if(a == 0)
		{
			printf("%lld %lld\n", x1 + 1, y1);
			continue;
		}
		if(b == 0)
		{
			printf("%lld %lld\n", x1, y1 + 1);
			continue;
		}

		ll A, B, d, x, y;
		A = b, B = -a;
		exgcd(A, B, d, x, y);

		printf("%lld %lld\n", x + x1, y + y1);
	}

	return 0; 
} 

J. Permutation Puzzle(拓扑排序+区间覆盖)

这题妙啊

首先跑一个正向的拓扑排序和反向的拓扑排序确定每个数的取值范围。

接下来就是给这n个区间每个区间分配一个数,使得每个数互不相同,同时要满足拓扑关系

不考虑拓扑关系的话,直接贪心,即从小到大枚举,对于每个包含它的区间,选一个端点最小的,用一个set维护即可

对于拓扑关系,边(u, v)使得Lu + 1 <= Ru,Ru + 1 <= Rv  即u的L和R都会更小一些,这样使得它一定比v先匹配到,即满足pu < pv,刚好是满足拓扑关系的

妙啊

#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 = 2e5 + 10;
int in[N], out[N], L[N], R[N];
int ans[N], id[N], n, m;
vector<int> g[N], G[N];

bool cmp(int a, int b)
{
	return L[a] < L[b];
}

int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		scanf("%d%d", &n, &m);
		_for(i, 1, n)
		{
			int x; scanf("%d", &x);
			if(x) L[i] = R[i] = x;
			else L[i] = 1, R[i] = n;
			g[i].clear();
			G[i].clear();
			in[i] = out[i] = 0;
		}
		while(m--)
		{
			int u, v;
			scanf("%d%d", &u, &v);
			g[u].push_back(v);
			G[v].push_back(u);
			in[v]++; out[u]++;
		}

		queue<int> q;
		_for(i, 1, n)
			if(!in[i])
				q.push(i);
		while(!q.empty())
		{
			int u = q.front(); q.pop();
			for(int v: g[u])
			{
				L[v] = max(L[v], L[u] + 1);
				if(--in[v] == 0) q.push(v);
			}
		}

		_for(i, 1, n)
			if(!out[i])
				q.push(i);
		while(!q.empty())
		{
			int u = q.front(); q.pop();
			for(int v: G[u])
			{
				R[v] = min(R[v], R[u] - 1);
				if(--out[v] == 0) q.push(v);
			}
		}

		_for(i, 1, n) id[i] = i;
		sort(id + 1, id + n + 1, cmp);

		int flag = 1, pos = 1;
		set<pair<int, int>> s;
		_for(i, 1, n)
		{
			while(pos <= n && L[id[pos]] <= i)
			{
				s.insert({R[id[pos]], id[pos]});
				pos++;
			}
			if(!s.size() || s.begin()->first < i)
			{
				flag = 0;
				break;
			}
			ans[s.begin()->second] = i;
			s.erase(s.begin());
		}

		if(!flag) puts("-1");
		else
		{
			_for(i, 1, n) printf("%d ", ans[i]);
			puts("");
		}
	}

	return 0; 
} 

C. Array Concatenation(找规律)

首先进行一次b操作后就回文了,接下来两种操作都一样了

然后可以发现第二次操作在哪里答案都是一样的

#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 = 1e9 + 7;
const int N = 1e6 + 10;
int a[N], n, m;
ll f[N];

pair<ll, ll> op1(pair<ll, ll> x, int k, ll n)
{
	if(!k) return x;
	ll s = x.first, ss = x.second;
	return {f[k] * s % mod, (f[k] * ss % mod + (f[k] - 1) * f[k - 1] % mod * s % mod * n % mod) % mod};
}

pair<ll, ll> op2(pair<ll, ll> x, ll n)
{
	ll s = x.first, ss = x.second;
	return {2 * s % mod, (2 * n + 1) * s % mod};
}

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

	f[0] = 1;
	_for(i, 1, 1e6) f[i] = f[i - 1] * 2 % mod;

	ll s = 0, ss = 0;
	_for(i, 1, n)
	{
		(s += a[i]) %= mod;
		(ss += 1LL * a[i] * (n - i + 1)) %= mod;
	}

	pair<ll, ll> cur = {s, ss};
	ll ans = op1(cur, m, n).second; 

	pair<ll, ll> t = cur;
	t = op2(t, n);
	t = op1(t, m - 1, 2 * n);
	ans = max(ans, t.second);
	printf("%lld\n", ans);

	return 0; 
} 

周五

Link with Bracket Sequence II(区间dp)

数据范围看起来就是n^3,直接区间dp

dp[l][r]表示l到r的合法括号匹配

那么按照题目,枚举l和哪一个匹配

如果和r匹配,那么就是l和r匹配的方案数乘上dp[l + 1][r - 1]

如果和中间的一个k匹配,拿就是l和k匹配的方案数乘dp[l + 1][k - 1] 乘上dp[k + 1][r]

#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 = 500 + 10;
const int mod = 1e9 + 7;
ll dp[N][N], m;
int a[N], n;

ll cal(int i, int j)
{
	if(a[i] < 0 || a[j] > 0) return 0;
	if(!a[i] && !a[j]) return m;
	if(!a[i] || !a[j]) return 1;
	if(a[i] + a[j] == 0) return 1;
	return 0;
}

ll get(int l, int r)
{
	if(l + 1 == r) return cal(l, r);
	return cal(l, r) * dp[l + 1][r - 1] % mod;
}

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

		for(int len = 2; len <= n; len += 2)
			_for(l, 1, n)
			{
				int r = l + len - 1;
				if(r > n) break;

				dp[l][r] = get(l, r);
				_for(k, l + 1, r - 2)
					(dp[l][r] += get(l, k) * dp[k + 1][r] % mod) %= mod;
			}
		printf("%lld\n", dp[1][n]);
	}

	return 0; 
} 

B. Link with Running(最短路+缩点+最长路)

先跑一个最短路,最短路图是一个DAG,直接拓扑排序+dp求最长路

然后WA了,发现因为有0权边,所以有环

尝试了一发SPFA,看会不会被卡,果然被卡了

就只能缩点+拓扑排序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;

typedef long long ll;
const int N = 1e5 + 10;
struct Edge 
{ 
	int v; ll w, p; 
	bool operator < (const Edge& rhs) const
	{
		return w > rhs.w;
	}
};
vector<Edge> g[N];
int deg[N], vis[N], n, m;
ll d[N], dp[N];
int dfn[N], low[N], st[N], co[N], top, cnt, id;
vector<pair<int, int>> G[N];

void dfs(int u)
{
	dfn[u] = low[u] = ++cnt;
	st[++top] = u;

	for(auto [v, w, p]: g[u])
		if(d[v] == d[u] + w)
		{
			if(!dfn[v])
			{
				dfs(v);
				low[u] = min(low[u], low[v]);
			}
			else if(!co[v]) low[u] = min(low[u], dfn[v]);
		}
	
	if(low[u] == dfn[u])
	{
		id++;
		while(1)
		{
			co[st[top--]] = id;
			if(st[top + 1] == u) break;
		}
	}
}

void solve()
{
	_for(i, 1, n) d[i] = 1e18;
	d[1] = 0;
	priority_queue<Edge> q;
	q.push({1, d[1], 0});

	while(!q.empty())
	{
		Edge x = q.top(); q.pop();
		int u = x.v;
		if(d[u] != x.w) continue;

		for(auto [v, w, p]: g[u])
			if(d[v] > d[u] + w)
			{
				d[v] = d[u] + w;
				q.push({v, d[v], 0});
			}
	}
}

int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		cnt = top = id = 0;
		scanf("%d%d", &n, &m);
		_for(i, 1, n) g[i].clear(), G[i].clear(), low[i] = dfn[i] = co[i] = dp[i] = deg[i] = 0;
		while(m--)
		{
			int u, v, w, p;
			scanf("%d%d%d%d", &u, &v, &w, &p);
			g[u].push_back({v, w, p});
		}

		solve();
		printf("%lld ", d[n]);

		_for(i, 1, n)
			if(!dfn[i])
				dfs(i);
		_for(u, 1, n)
			for(auto [v, w, p]: g[u])
				if(d[v] == d[u] + w)
					if(co[u] != co[v])
					{
						G[co[u]].push_back({co[v], p});
						deg[co[v]]++;
					}
		
		queue<int> q;
		_for(i, 1, id)
			if(!deg[i])
				q.push(i);
		while(!q.empty())
		{
			int u = q.front(); q.pop();
			for(auto [v, p]: G[u])
			{
				dp[v] = max(dp[v], dp[u] + p);
				if(--deg[v]== 0) q.push(v);
			}
		}
		printf("%lld\n", dp[co[n]]);
	}

	return 0; 
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值