//https://img2018.cnblogs.com/blog/1646268/201908/1646268-20190806114008215-138720377.jpg

2023.7.24结题报告

20237.24结题报告

T1

考试的时候没想到正解,于是打了一个暴力。

暴力的思路就是预处理出前缀异或和,然后直接暴力枚举两个端点然后判断区间内异或和是不是当前 \(k\) 的一个因子。

复杂度高,但是暴力分有 \(80\).

#include <bits/stdc++.h>

#define int long long
#define N 1000100

using namespace std;

inline int read(){int x=0,f=1;char ch=getchar();while(!isdigit(ch)){f=ch!='-';ch=getchar();}while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return f?x:-x;}

int n, k, a[N], sum[N], t[N], ans;

signed main()
{
	n = read(), k = read();
	for(int i = 1; i <= n; i ++) a[i] = read(), sum[i] = a[i] ^ sum[i - 1];
	for(int i = 1; i <= k; i ++)
		if(k % i == 0) t[i] = 1;
	for(int i = 1; i <= n; i ++)
	{
		for(int j = i; j <= n; j ++)
		{
			int xx = sum[i - 1] ^ sum[j];
			if(t[xx]) ans ++;
		}
	}
	cout << ans << endl;
	return 0;
}

正解:

我们知道 \(k\) 的因子完全可以只枚举到 \(\sqrt{k}\),然后两个两个的判断,我们直接把所有的前缀和都存到桶里然后我们就可以直接调用从 \(1\) 往后有多少点的异或和是要查的数。

我们直接在后面枚举 \(k\) 的因子,然后能整除的,我们再枚举 \(n\),我们直接查询当前\(k\) 的两个因子与当前的枚举到的前缀和数组的异或的区间的个数,因为这样就相当于两个区间 \(1\) 以外另一个点的异或和那个和当前枚举的异或起来是当前枚举到的因子。

\(k\) 是完全平方数的时候要特判,一开始可以从 \(0\) 开始桶里 \(0\) 的初值是 \(1\)

#include <bits/stdc++.h>

#define int long long
#define N 1000100

using namespace std;

int n, k, a[N], t[N], ans;

signed main()
{
	cin >> n >> k;
	t[0] ++;
	for(int i = 1; i <= n; i ++)
		cin >> a[i], a[i] ^= a[i - 1], t[a[i]] ++;
	for(int i = 1; i * i <= k; i ++)
	{
		if(k % i != 0) continue;
		int res = 0;
		for(int j = 0; j <= n; j ++)
			res += t[a[j] ^ i] + t[a[j] ^ (k / i)];
		if(i * i == k) res /= 2;
		ans += res;
	}
	cout << ans / 2 << endl;
	return 0;
}

T2

考试没想出来怎么线段树,然后就直接暴力枚举区间端点了。但是挂了。

#include <bits/stdc++.h>

#define int long long
#define DB double
#define N 1000100
#define endl '\n'

using namespace std;

inline int read(){int x=0,f=1;char ch=getchar();while(!isdigit(ch)){f=ch!='-';ch=getchar();}while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return f?x:-x;}

int n, m, a[N], sum[N];

signed main()
{
	n = read(), m = read();
	for(int i = 1; i <= n; i ++) a[i] = read(), sum[i] = sum[i - 1] + a[i];
	while(m --)
	{
		int op = read();
		if(op == 1)
		{
			int l = read(), r = read(), x = read();
			for(int i = l; i <= r; i ++)
				a[i] += x;
			for(int i = l; i <= n; i ++)
				sum[i] = sum[i - 1] + a[i];
		}
		else
		{
			int l = read(), r = read();
			int fz = 0, fm = 1;
			for(int i = l; i <= r; i ++)
			{
				for(int j = i + 1; j <= r; j ++)
				{
					int x = sum[j] - sum[i - 1];
					int y = j - i + 1;
					if((DB)(x * 1.0 / y) > (DB)(fz * 1.0 / fm))
					{
						int g = __gcd(x, y);
						fz = x / g;
						fm = y / g;
					}
				}
			}
			cout << fz << "/" << fm << endl;
		}
	}
	return 0;
} 

其实我们思考一下,发现区间内的数一定不超过三个数,因为我们发现,如果要是选多了,当前的平均数,一定能分成两个一大一小的部分然后得出当前的平均数。

所以我们直接开两个线段树,一个维护两个点的情况,一个维护三个点的情况。

我们都知道,这个是有边界问题的,所以我们在进行操作的书写的时候很难受,分为好几种情况:两个的:只有一个点,在右边或者左边,中间的是两个两个的。三个点:只有一个点,只有两个点,中间部分都是三个点三个点的那种。

然后查询,我们只有两个点的话要特判,只有两个点,那就是只能那一个了,如果要是大于三个点的话,三个点和两个点的情况都要考虑然后输出。

好难写。

#include <bits/stdc++.h>

#define int long long
#define N 1000100
#define endl '\n' 

using namespace std;

inline int read(){int x=0,f=1;char ch=getchar();while(!isdigit(ch)){f=ch!='-';ch=getchar();}while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return f?x:-x;}

int n, m, A[N], B[N];

struct Tree
{
#define ls (x << 1)
#define rs (x << 1 | 1)

int v[N << 2], tag[N << 2];

inline void p_p(int x) {v[x] = max(v[ls], v[rs]);}//v是区间内最大值 

inline void p_d(int x)//下传懒标记 
{
	if(!tag[x]) return ;//没有就直接退出 
	v[ls] += tag[x];//懒标记给儿子 
	v[rs] += tag[x];
	tag[ls] += tag[x];
	tag[rs] += tag[x];//修改儿子的懒标记 
	tag[x] = 0;//清空懒标记 
	return ;
}

inline void build(int x, int l, int r)
{ 
	if(l == r) return v[x] = B[l], void();//将数组在树上赋值 
	int mid = l + r >> 1;
	build(ls, l, mid);
	build(rs, mid + 1, r);
	p_p(x);
	return ;
}

inline int ask(int x, int l, int r, int nl, int nr)
{
	if(l >= nl && r <= nr) return v[x];//如果包含当前的区间就直接返回当前点的值 
	int mid = l + r >> 1;
	p_d(x);
	if(mid >= nr) return ask(ls, l, mid, nl, nr);//超过右边界就去右边找 
	if(mid < nl) return ask(rs, mid + 1, r, nl, nr);//超过左边界就去左边找 
	return max(ask(ls, l, mid, nl, nr), ask(rs, mid + 1, r, nl, nr));//中点在里面就两段取个max 
}

inline void add(int x, int l, int r, int nl, int nr, int k)
{
	if(l >= nl && r <= nr) return v[x] += k, tag[x] += k, void();//包含当前区间,直接修改 
	int mid = l + r >> 1;
	p_d(x);
	if(mid >= nl) add(ls, l, mid, nl, nr, k);
	if(mid < nr) add(rs, mid + 1, r, nl, nr, k);
	p_p(x);
}

} tt[2];//两棵线段树 

signed main()
{
	n = read(), m = read();
	for(int i = 1; i <= n; i ++)
		A[i] = read();
	for(int i = 1; i <= n; i ++)
		B[i] = A[i] + A[i + 1];
	tt[0].build(1, 1, n);//第一颗树是维护两个两个的情况 
	for(int i = 1; i <= n; i ++)
		B[i] += A[i + 2];//加上第三种的情况 
	tt[1].build(1, 1, n);//第二棵线段树是维护三个三个的情况 
	while(m --)
	{
		int op = read(), l = read(), r = read(), x;
		if(op == 1)
		{
			x = read();
			if(l <= r - 1) tt[0].add(1, 1, n, l, r - 1, 2 * x);//两个都包含的情况 
			if(l - 1 >= 1) tt[0].add(1, 1, n, l - 1, l - 1, x);//这个只包含后面那个 
			if(r <= n) tt[0].add(1, 1, n, r, r, x);//这个是只有r一个点的情况 
			if(l <= r - 2) tt[1].add(1, 1, n, l, r - 2, 3 * x);//三个点都包含的 
			if(l - 1 >= 1) tt[1].add(1, 1, n, l - 1, l - 1, 2 * x);//这个是左边有一个的情况 
			if(r - 1 >= 1) tt[1].add(1, 1, n, r - 1, r - 1, 2 * x);//只有右边两个点 
			if(l - 2 >= 1) tt[1].add(1, 1, n, l - 2, l - 2, x);//只有l一个点加上 
			if(r <= n) tt[1].add(1, 1, n, r, r, x);//r一个点加上x 
		}
		else//输出操作 
		{
			if(r - l == 1)//只有两个点 
			{
				int ans = tt[0].ask(1, 1, n, l, l);//输出两个点最大的 
				if(ans % 2 == 0) cout << ans / 2 << "/1" << endl;//如果能整除就输出1 
				else cout << ans << "/2" << endl;//不能整除原来输出 
			}
			else
			{
				int ans = max(3 * tt[0].ask(1, 1, n, l, r - 1), 2 * tt[1].ask(1, 1, n, l, r - 2));//两个点的情况和三个点的情况取一个大的 
				int div = 6;//分母 
				if(ans % 2 == 0) ans /= 2, div /= 2;//能被2整除 
				if(ans % 3 == 0) ans /= 3, div /= 3;//能被3整除 
				cout << ans << "/" << div << endl;//输出 
			}
		}
	}
	return 0;
}

T3

打了一个暴力,每一次直接枚举两个区间端点,然后判断是不是等于 \(x\),然后直接枚举找答案。

#include <bits/stdc++.h>

#define int long long
#define N 1000100
#define endl '\n'

using namespace std;

inline int read(){int x=0,f=1;char ch=getchar();while(!isdigit(ch)){f=ch!='-';ch=getchar();}while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return f?x:-x;}

int n, m, a[N];

signed main()
{
	n = read(), m = read();
	for(int i = 1; i <= n; i ++) a[i] = read();
	while(m --)
	{
		int op = read(), l = read(), r = read(), x = read();;
		if(op == 1) a[l] = x;
		else
		{
			int ans = 0;
			for(int i = l; i <= r; i ++)
				for(int j = i + 1; j <= r; j ++)
				{
					if(a[i] != x || a[j] != x) continue; 
					for(int k = i; k < j; k ++)
						if(a[k] != a[k + 1]) ans ++;
				}
			cout << ans << endl;
		}
	}
	return 0;
}

T4

考试的时候还是只会暴力。

枚举每一个操作,操作完直接遍历一遍都修改掉,注意有可能后面的被修改了,所以要先存起来然后后面再计算即可。

听说我的代码莫名其妙多卡过去一个点(?

#include <bits/stdc++.h>

//#define int long long
#define N 1000100
#define endl '\n'

using namespace std;

inline int read(){int x=0,f=1;char ch=getchar();while(!isdigit(ch)){f=ch!='-';ch=getchar();}while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return f?x:-x;}

int n, m, a[N], t[N], v[N], last;

signed main()
{
	n = read();
	for(register int i = 1; i <= n; i ++) a[i] = read();
	m = read();
	while(m --)
	{
		int x = read();
		x ^= last;
		t[x] ++;
		cout << t[x] << endl;
		last = t[x];
//		int cao = t[x];
//		t[x] -= cao;
//		t[a[x]] += cao;
		for(register int i = 1; i <= n; i ++) v[i] = t[i];
		for(register int i = 1; i <= n; i ++)
			t[i] -= v[i], t[a[i]] += v[i];
	}
	return 0;
}

posted @ 2023-07-24 22:19  北烛青澜  阅读(8)  评论(0)    收藏  举报