大三第十一周学习笔记

周一

赛前最后的努力了,加油!!

前几天疯狂写作业,现在还有三场比赛的题没补,加油!

L. Buy Figurines(堆+线段树)

这题的关键在于只有n个人,怎么利用这个保证复杂度

考虑维护这n个人的离开时间,用一个优先队列维护

那么对于当前状态,可以处理处当前每个队的人数,怎么快速求最值呢

这个其实就是一个可以修改的堆,可以用set或者线段树实现

#include
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#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;
typedef pair pii;
const int N = 2e5 + 10;
pair b[N];
int t[N << 2], n, m;
ll last[N];

void up(int k)
{
	t[k] = min(t[l(k)], t[r(k)]);
}

void clear(int k, int l, int r)
{
	t[k] = 0;
	if(l == r) return;
	int m = l + r >> 1;
	clear(l(k), l, m);
	clear(r(k), m + 1, r);
}

void modify(int k, int l, int r, int x, int p)
{
	if(l == r)
	{
		t[k] += p;
		return;
	}
	int m = l + r >> 1;
	if(x <= m) modify(l(k), l, m, x, p);
	else modify(r(k), m + 1, r, x, p);
	up(k);
}

int ask(int k, int l, int r)
{
	if(l == r) return l;
	int m = l + r >> 1;
	if(t[l(k)] <= t[r(k)]) return ask(l(k), l, m);
	else return ask(r(k), m + 1, r);
}

int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		scanf("%d%d", &n, &m);
		_for(i, 1, n) scanf("%d%d", &b[i].first, &b[i].second);
		sort(b + 1, b + n + 1);
		
		_for(i, 1, m) last[i] = 0;
		clear(1, 1, m);

		ll ans = 0;
		priority_queue, greater> q;
		_for(i, 1, n)
		{
			auto [a, s] = b[i];
			while(!q.empty() && a >= q.top().first)
			{
				modify(1, 1, m, q.top().second, -1);
				q.pop();
			}

			int cur = ask(1, 1, m);
			if(a >= last[cur]) last[cur] = a + s;
			else last[cur] += s;
			ans = max(ans, last[cur]);
			modify(1, 1, m, cur, 1);
			q.push({last[cur], cur});
		}
		printf("%lld\n", ans);
	}

	return 0; 
} 

C. Slipper(建图+最短路)

每一层加一个点,这一层连到它,然后它连向d+k层的所有点,然后跑最短路即可

#include
#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 = 2e6 + 10;
ll d[N];
struct node
{
	int v; ll w;
	bool operator < (const node& rhs) const
	{
		return w > rhs.w;
	}
};
vector> g[N];
vector ve[N];
int n, k, p, s, t, cnt;

void dfs(int u, int fa, int dep)
{
	ve[dep].push_back(u);
	for(auto [v, w]: g[u])
	{
		if(v == fa) continue;
		dfs(v, u, dep + 1);
	}
}

void solve()
{
	_for(i, 1, 2 * n) d[i] = 1e18;
	d[s] = 0;
	priority_queue q;
	q.push({s, d[s]});

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

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

int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		scanf("%d", &n);
		_for(i, 1, 2 * n) g[i].clear(), ve[i].clear();
		_for(i, 1, n - 1)
		{
			int u, v, w;
			scanf("%d%d%d", &u, &v, &w);
			g[u].push_back({v, w});
			g[v].push_back({u, w});
		}
		scanf("%d%d%d%d", &k, &p, &s, &t);

		dfs(1, 0, 1);

		cnt = n;
		_for(i, 1, n)
		{
			if(!ve[i].size()) break;
			cnt++;
			for(int x: ve[i]) g[x].push_back({cnt, 0}); 
			for(int x: ve[i + k]) g[cnt].push_back({x, p});
			if(i - k >= 1) for(int x: ve[i - k]) g[cnt].push_back({x, p});
		}

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

	return 0; 
} 

J. Eat, Sleep, Repeat(博弈)

这是一类博弈题,就是说操作次数其实是一定的,直接算操作次数的奇偶即可,关键是怎么算。

对于这题,可以发现限制为0把数分成了很多段。

每一段内,每个数尽可能的小,从最小的数开始往上。

#include
#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;
map mp;
int n, k;
ll a[N];

ll cal(vector ve, int st)   //函数这里不要忘记开long long
{
	if(!ve.size()) return 0;

	ll sum = 0;
	for(auto x: ve) sum += x - st;

	int pos = st - 1;
	while(mp.count(pos + 1) && mp[pos + 1]) pos++;
	if(pos == st - 1) return sum;

	ll res = 0, num = ve.size();
	_for(i, st, pos) 
	{
		if(num <= mp[i])
		{
			res += 1LL * (i - st) * num;   //相乘的时候注意开1LL
			num = 0;
			break;
		}
		else
		{
			res += 1LL * (i - st) * mp[i];
			num -= mp[i];
		}
	}
	res += num * (pos + 1 - st);

	return sum - res;
}

int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		mp.clear();
		scanf("%d%d", &n, &k);
		_for(i, 1, n) scanf("%lld", &a[i]);
		sort(a + 1, a + n + 1);
		
		vector ve;
		while(k--)
		{
			int x, y;
			scanf("%d%d", &x, &y);
			mp[x] = y;
			if(!y) ve.push_back(x);
		}
		ve.push_back(-1);
		ve.push_back(2e9);
		sort(ve.begin(), ve.end());

		ll ans = 0;
		rep(i, 0, ve.size() - 1)
		{
			int l = lower_bound(a + 1, a + n + 1, ve[i]) - a;
			int r = lower_bound(a + 1, a + n + 1, ve[i + 1]) - a - 1;
			vector v;
			_for(j, l, r) v.push_back(a[j]);
			ans += cal(v, ve[i] + 1);
		}
		puts(ans % 2 == 1 ? "Pico" : "FuuFuu");
	}

	return 0;
}

G. Grade 2(打表/异或)

比赛时是推出结论的

其实如果打表找一下规律,能出的更快

首先异或与加法联系紧密

对于A^x对A的改变为正负x

可以这么理解,异或的作用就是为1的地方取反,那么最多就是全部把0变1,也就是加x,相反最小就是减x。

范围宽放一点话就是二进制位

那么kx^x=kx+a

gcd(kx ^ x, x) = gcd(kx + a, x) = gcd(a, x)

也就是说其实gcd取决于异或后改变了多少

那么题目给的x是1e6,这个非常关键,意味着不会改变太多

再考虑二进制位,会发现如果k的范围可以遍历二进制位的所有数,那么一定会有重复,那么重复就可以想到循环节

考虑t为大于x的最小的二次幂数

那么kx = (k + t) x  (mod t)

因为改变的就是低位的二进制,所以只用看低位二进制的,那么发现t是循环 节

也就是说,gcd(kx ^ x, x)以t为循环节

那么l和r转化为前缀和

对于当前,看有几个t,多出来的部分用前缀和

#include
#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 = 2e6 + 10;
ll s[N], mod;

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

ll cal(ll r)
{
	ll k = r / mod;
	return k * s[mod] + s[r - k * mod];
}

int main()
{
	ll x, n;
	scanf("%lld%lld", &x, &n);
	
	ll t = 1;
	while(t <= x) t <<= 1;
	
	mod = t;
	_for(i, 1, mod) s[i] = s[i - 1] + (gcd(i * x ^ x, x) == 1);

	while(n--)
	{
		ll l, r;
		scanf("%lld%lld", &l, &r);
		printf("%lld\n", cal(r) - cal(l - 1));
	}

	return 0;
}

I. Dragon Bloodline(二分答案+贪心)

这题的重点是贪心

首先有一个结论,就是对于当前最高的2次幂,如果把它分配给大于等于它的,那么一定不会更差。因为在一个合法解里面,可以进行替换。

那么2的次幂从高到低遍历,首先把大于等于它的都分配给它,如果用完了就下一个,否则把多出来的给前cnt大的,这样肯定是浪费最少的。

二分答案的上界需要思考一下,要小心check的时候爆long long

分母部分为b的和,分子部分为maxa,这是上界。这样子check的时候不会爆long long

#include
#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 = 5e4 + 10;
int a[N], b[25], cnt[25], n, k;
ll c[N];
 
bool check(ll key)
{
	_for(i, 1, n) c[i] = a[i] * key;
	_for(i, 1, k) cnt[i] = b[i];
	
	for(int j = k; j >= 1; j--)
	{
		_for(i, 1, n)
		{
			ll cur = min(c[i] / (1 << (j - 1)), (ll)cnt[j]);
			cnt[j] -= cur;
			c[i] -= cur * (1 << (j - 1));
			if(!cnt[j]) break;
		}
 
		if(cnt[j])
		{
			cnt[j] = min(cnt[j], n);
			nth_element(c + 1, c + cnt[j], c + n + 1, greater());
			_for(i, 1, cnt[j]) c[i] = 0;
		}
	}
	
	_for(i, 1, n)
		if(c[i])
			return false;
	return true;
}
 
int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		scanf("%d%d", &n, &k);
		_for(i, 1, n) scanf("%d", &a[i]);
		_for(i, 1, k) scanf("%d", &b[i]);
		
		int mx = 0;
		_for(i, 1, n) mx = max(mx, a[i]);
		
		ll l = 0, r = 2e15 / mx;
		while(l + 1 < r)
		{
			ll m = l + r >> 1;
			if(check(m)) l = m;
			else r = m;
		}
		printf("%lld\n", l);
	} 
	
	return 0;
}

C. Magic(差分约束)

推一下三个不等式即可

注意每个位置非负有Si - Si-1 >= 0

这道题是求一个变量的最值,那么建立一个超级源点,对所有点连边权为0的边,然后从这个点开始spfa。注意不要漏掉0

养成好习惯,打完代码后不急着编译,从头肉眼查错,重点看有没有变量名打错

#include
#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 = 1e4 + 10;
int d[N], vis[N], cnt[N], S, n, k;
vector> g[N];

bool spfa()
{
	_for(i, 0, n + 1) d[i] = -1e9, vis[i] = cnt[i] = 0;  //最长路,有负权边,初始化为负无穷
	d[S] = 0;
	deque q;
	q.push_back(S);

	while(!q.empty())
	{
		int u = q.front(); q.pop_front();
		vis[u] = 0;
		for(auto [v, w]: g[u])
			if(d[v] < d[u] + w)   //注意这里是最长路
			{
				d[v] = d[u] + w;
				if(!vis[v])
				{
					if(!q.empty() && d[v] > d[q.front()]) q.push_front(v);  //注意判空
					else q.push_back(v);
					vis[v] = 1;
					if(++cnt[v] > n) return false;
				}
			}
	}
	return true;
}

int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		scanf("%d%d", &n, &k);
		_for(i, 0, n + 1) g[i].clear();
		S = n + 1;
		_for(i, 1, n)
		{
			int p; scanf("%d", &p);
			g[max(i - k, 0)].push_back({min(i + k - 1, n), p});
			g[i - 1].push_back({i, 0});
		}
		_for(i, 0, n) g[S].push_back({i, 0});

		int q; scanf("%d", &q);
		_for(i, 1, q)
		{
			int l, r, b;
			scanf("%d%d%d", &l, &r, &b);
			g[r].push_back({l - 1, -b});
		}

		if(!spfa()) puts("-1");
		else printf("%d\n", d[n]);
	} 
	
	return 0;
}

Link with Level Editor II(矩阵+线段树)

首先图比较小,可以用矩阵乘法。两个矩阵相乘就是恰好就是计算方案数,于是可以用矩阵来维护。

容易发现可以双指针求,关键是如何迅速求一段的值

可以直接用线段树求,但是会T

因为只关心1到m的答案,所以可以用一个向量不断右乘矩阵,这也可以优化掉一个m

继续养成习惯,写完检查一遍,防止变量。

#include
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#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 = 5e3 + 10;
struct martix 
{ 
	ll s[25][25]; 
	martix() { memset(s, 0, sizeof s); }
}t[N << 2];
int n, m, k;
ll v[25];

martix mul(martix A, martix B)
{
	martix C;
	_for(i, 1, m)
		_for(j, 1, m)
			_for(k, 1, m)
				C.s[i][j] += A.s[i][k] * B.s[k][j];
	return C;
}

void mul(martix A)
{
	ll res[25] = {0};
	_for(i, 1, m)
		_for(j, 1, m)
			res[i] += v[j] * A.s[j][i];
	_for(i, 1, m) v[i] = res[i];
}

void up(int k)
{
	t[k] = mul(t[l(k)], t[r(k)]);
}

void build(int k, int l, int r)
{
	if(l == r)
	{
		int l; scanf("%d", &l);
		memset(t[k].s, 0, sizeof t[k].s);
		_for(i, 1, m) t[k].s[i][i] = 1;
		while(l--)
		{
			int u, v;
			scanf("%d%d", &u, &v);
			t[k].s[u][v] = 1;
		}
		return;
	}
	
	int m = l + r >> 1;
	build(l(k), l, m);
	build(r(k), m + 1, r);
	up(k);
}

void ask(int k, int l, int r, int L, int R)
{
	if(L <= l && r <= R)
	{
		mul(t[k]);
		return;
	}
	int m = l + r >> 1;
	if(L <= m) ask(l(k), l, m, L, R);
	if(R > m) ask(r(k), m + 1, r, L, R);
}

bool check(int l, int r)
{
	_for(i, 1, m) v[i] = 0;
	v[1] = 1;

	ask(1, 1, n, l, r);
	return v[m] <= k;
}

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

		int r = 0, ans = 0;
		_for(l, 1, n)
		{
			while(r + 1 <= n && check(l, r + 1)) r++;
			ans = max(ans, r - l + 1);
		}
		printf("%d\n", ans);
	} 
	
	return 0;
}

还有一种骚操作可以避免删除。

在l和r中加一个mid,每次计算拆成[l, mid] 乘上 [mid + 1, r]

右半部分r拓展的时候维护

左半部分用一个bi维护i到mid的值

当l超过mid的时候,mid=r,然后计算bi。

这样依然是O(n)的,同时避免了删除操作

#include
#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 = 5e3 + 10;
struct martix 
{ 
	ll s[25][25]; 
	martix() { memset(s, 0, sizeof s); }
}a[N], b[N];
int n, m, k;

martix mul(martix A, martix B)
{
	martix C;
	_for(i, 1, m)
		_for(j, 1, m)
			_for(k, 1, m)
				C.s[i][j] += A.s[i][k] * B.s[k][j];
	return C;
}

void init(martix& A)  //初始化为单位矩阵 注意引用
{
	memset(A.s, 0, sizeof A.s);
	_for(i, 1, m) A.s[i][i] = 1;
}

int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		scanf("%d%d%d", &n, &m, &k);
		_for(i, 1, n)
		{
			init(a[i]);
			int l; scanf("%d", &l);
			while(l--)
			{
				int u, v;
				scanf("%d%d", &u, &v);
				a[i].s[u][v] = 1;
			}
		}

		int l = 1, mid = 1, r = 1, ans = 0;
		martix cur; init(cur);
		b[1] = a[1];

		_for(l, 1, n)
		{
			if(l > mid)
			{
				mid = r;
				init(cur);
				martix t; init(t);
				for(int i = mid; i >= l; i--)
				{
					t = mul(a[i], t);
					b[i] = t;
				}
			}
			while(r + 1 <= n && mul(b[l], mul(cur, a[r + 1])).s[1][m] <= k) cur = mul(cur, a[++r]);
			ans = max(ans, r - l + 1);
		}
		printf("%d\n", ans);
	} 
	
	return 0;
}

你可能感兴趣的:(学习)