SDNU_Winter_Practice_3_20230106「数论(鸽」「dfs」「树状数组+数学」「二分答案+前缀和贪心」

6/13 数据结构真是要命。

C.SPOJ.com - Problem NUMTRY

思路:

数论,不会证,记住定理吧。参考参考:

最大公约数和最小公倍数_ACdreamers的博客-CSDN博客

之后还要pollard_rho吧大概,剩下C不动了。反正直接筛喜提tle。

G.E - Round Trip (atcoder.jp)

 思路1(赛时一血!):

一开始写了标准的回溯dfs又wa又tle,后来想了想发现,如果走到某一步发现无路可走了,那必然是他走到死路上了,从其他任意 . 必不可能再次走到他,所以这个不用回溯,而且在之前搜索过的基础上继续搜索会减小很多重复程序,降低时间复杂度。

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define mem(a,b) memset(a,b,sizeof a)
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 3e4 + 50;
char arr[N][N];
bool vis[N][N];
int dx[] = {1, 0, -1, 0};

int dy[] = {0, 1, 0, -1};
int x, y;
int n, m;

bool judge(int a, int b) {
	if (a > n || b > m || a < 1 || b < 1 || vis[a][b] == 1 || arr[a][b] == '#')
		return 0;
	else
		return 1;
}

bool dfs(int a, int b, int step) {
	if (a == x && b == y && step > 1)
		return 1;
	vis[a][b] = 1;
	for (int i = 0; i < 4; ++i) {
		int tx = a + dx[i], ty = b + dy[i];
		if (judge(tx, ty)) {
			//cout << tx << " " << ty << '\n';
			if (tx == x && ty == y && step == 0)//防止第一步走到S
				continue;
			if (dfs(tx, ty, step + 1))
				return 1;
		}
	}
	return 0;
}

void work() {
	cin >> n >> m;
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= m; ++j) {
			cin >> arr[i][j];
			if (arr[i][j] == 'S') {
				x = i;
				y = j;
			}
			if (arr[i][j] == '#') {
				vis[i][j] = 1;
			}
		}
	}
	for (int i = 0; i < 4; ++i) {
		int tx = x + dx[i], ty = y + dy[i];
		if (judge(tx, ty)) {
			//cout << tx << " 0 " << ty << '\n';
			if (dfs(tx, ty, 0)) {
				cout << "Yes\n";
				return;
			}
		}
	}
	cout << "No\n";
}

signed main() {
	io;
	work();
	return 0;
}

/*
4 4
..##
.S.#
###.
....
*/

思路2(yjgg):

类似油田,标记相同的联通块为相同颜色,只需判断‘S'四周是否存在两个相同颜色的联通块即可。

一些其他小技巧:

  • 二维数组会开不下可以用map<pair<int,int>,int>存图
  • 涉及到回溯的dfs的时间复杂度一般是指数或阶乘形式的,只是遍历图的dfs时间复杂度就是图的大小,可以忽略不计。 
#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define pb push_back
#define m_p make_pair
#define mem(a,b) memset(a,b,sizeof a)
#define inf 0x3f3f3f3f
const int N = 1e6 + 10;
string mp[N];
map<pair<int, int>, int>col;
int dx[] = {0, 0, 1, -1};

int dy[] = {1, -1, 0, 0};
int x, y;
int n, m;

bool judge(int a, int b) {
	if (a > n || b > m || a < 1 || b < 1 || mp[a][b] == '#' )
		return 0;
	else
		return 1;
}

void dfs(int a, int b, int cur) {
	for (int i = 0; i < 4; ++i) {
		int tx = a + dx[i], ty = b + dy[i];
		if (judge(tx, ty)) {
			col[m_p(tx, ty)] = cur;
			mp[tx][ty] = '#';
			dfs(tx, ty, cur);
		}
	}
}

void work() {
	cin >> n >> m;
	for (int i = 1; i <= n; ++i) {
		string a;
		cin >> a;
		mp[i] = '0' + a;
	}
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= m; ++j) {
			if (mp[i][j] == 'S') {
				x = i;
				y = j;
				mp[i][j] = '#';
			}
		}
	}
	bool flag[4] = {0};
	for (int i = 0; i < 4; ++i) {
		int tx = x + dx[i], ty = y + dy[i];
		if (judge(tx, ty))
			flag[i] = 1;
	}

	int tot = 0;
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= m; ++j) {
			if (judge(i, j)) {
				tot++;
				col[m_p(i, j)] = tot;
				mp[i][j] = '#';
				dfs(i, j, tot);
			}
		}
	}
	vector<int>tt;
	for (int i = 0; i < 4; ++i) {
		int tx = x + dx[i], ty = y + dy[i];
		if (flag[i])
			tt.pb(col[m_p(tx, ty)]);
	}
	sort(tt.begin(), tt.end());
	for (int i = 1; i < tt.size(); ++i) {
		if (tt[i] == tt[i - 1]) {
			cout << "Yes\n";
			return;
		}
	}
	cout << "No\n";
}

int main() {
	io;
	work();
}

我真的很不喜欢dfs!!!!!!!!!!

F.F - Double Chance (atcoder.jp)

思路:

数学+树状数组。

分母:从K张卡片中抽卡,所有情况总量是K*K,即期望的分母

分子:每种情况的答案的求和:

        递推。由于每次增加只多了a[K]这一个卡片,即只多了2*K-1种抽法,所以K可以由K-1推来。而这2*K-1种抽法对答案的贡献可以分为两部分来计算,第一部分是x<=a[K],假设x<=a[K]的x的数量是num,这些数字对分子的贡献是(2*num+1)*a[K],第二部分是x>a[K],这些数字的对答案的贡献是这些数字的和,即前k-1个数字中出现的严格大于a[k]的所有的数字的和。

我们可以用两个树状数组分别维护num与和,对于每个答案,我们只需要再乘以i*i的逆元即可得到期望。请务必注意取模。

看题解看了几天发现其实我根本不算会树状数组和线段树,简单说说。

这个题其实只用了单点修改和区间查询,我们在递推的过程中建树,并且一定要先算数再加点。注意到建树的顺序和数的大小并无关系,所以令arr[i]直接作为数组下标,即可完成更新,在其后的数并未添加到树里,所以求和的时候不会求到他。但在每次的更新中,我们其实更新了所有比他大的上级的数,所以在推到某数时,该数之前的num已经更新完毕。

有关lowbit:

求和:(遍历与他同层且小于他的数)

 添加:(找祖宗)

 这样我们就可以完成更新辣!

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define m_p make_pair
#define mem(a,b) memset(a,b,sizeof a)
#define mod 998244353
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 5e5 + 50;
int arr[N];
int rk[N];//小于arr[k]的个数
int sum[N];//小于arr[k]的数和

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

int qpow(int a, int b) {
	a %= mod;
	int ans = 1;
	while (b > 0) {
		if (b & 1)
			ans = ans * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return ans;
}

int getnum1(int a) {
	int num = 0;
	while (a) {
		num += rk[a];
		num %= mod;
		a -= lowbit(a);
	}
	return num;
}

void update1(int a) {
	while (a < 4e5 + 50) {
		rk[a]++;
		rk[a] %= mod;
		a += lowbit(a);
	}
}

int getnum2(int a) {
	int num = 0;
	while (a) {
		num += sum[a];
		num %= mod;
		a -= lowbit(a);
	}
	return num;
}

void update2(int a) {
	int t = a;
	while (a < 4e5 + 50) {
		sum[a] += t;
		sum[a] %= mod;
		a += lowbit(a);
	}
}

void work() {
	int n;
	cin >> n;
	for (int i = 1; i <= n; ++i) {
		cin >> arr[i];
	}
	int ans = 0;
	for (int i = 1; i <= n; ++i) {
		int cnt = getnum1(arr[i]) % mod;
		update1(arr[i]);
		int sum = (getnum2(4e5 + 50) - getnum2(arr[i]) + mod) % mod;
    //小于4e5 + 50的所有数和-小于arr[i]的所有数和
		update2(arr[i]);
		ans = (ans + (cnt * 2 + 1) * arr[i] % mod + sum * 2 % mod) % mod;
		cout << (ans * qpow(i * i % mod, mod - 2) + mod ) % mod << '\n';
	}
}

signed main() {
	io;
	work();
	return 0;
}

I.Problem - 1119A - Codeforces 

思路:

找最后一个和1不一样的数和第一个和n不一样的数即可。 

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define pb push_back
#define m_p make_pair
#define mem(a,b) memset(a,b,sizeof a)
#define inf 0x3f3f3f3f
const int N = 3e5 + 10;
int arr[N];


void work() {
	int n;
	cin >> n;
	int num = 0;
	for (int i = 1; i <= n; ++i) {
		cin >> arr[i];
	}
	for (int i = n; i >= 2; --i) {
		if (arr[i] != arr[1]) {
			num = max(num, i - 1);
			break;
		}
	}
	for (int i = 1; i < n; ++i) {
		if (arr[i] != arr[n]) {
			num = max(num, n - i);
			break;
		}
	}
	cout << num << '\n';
}

int main() {
	io;
	work();
}

K.Problem - D - Codeforces

思路:

二分答案+前缀和贪心。

排序数组,二分最大中位数。

check过程:令原数组中小于x的数都为-1,大于x的数都为1,这样每个数的贡献都是1,可以直接计数,前缀和就代表前缀中在中位数前后的数的个数差(*),从第k项开始一边判断一边贪心(可以不要的)前缀,可得check。

(*):当字串个数为奇时,中位数是中间的数,在上述计算中为+1,故该串总和为1;当为偶数时,题目要求中位数是第k/2大数,在上述计算中为+1,故该串总和为2,可证明判断的成立条件。

另:注意二分的边界。最后输出l-1的原因是:假定答案是某一步的mid,此时l变为mid+1,之后的check永远不会成功,直到r在某次减小到mid,无法进入循环,此时答案为l-1;假定答案是某一步的mid-1,此时r变为mid-1,之后的check永远不会失败,直到l在某次增加为mid,此时答案为l-1;假定答案是某一步的mid+1,此时l变为mid+1,仅在l==r的循环中check会成功一次使l变为l+1跳出循环,此时答案仍为l-1。

(之前学的时候同桌就想了很久,好久不写二分答案我刚刚也想了一会儿,浅推一手,这里当个备忘录。)

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define m_p make_pair
#define mem(a,b) memset(a,b,sizeof a)
//#define mod 998244353
#define inf 0x3f3f3f3f
const int N = 1e6 + 5;
ll sum[N], arr[N], bb[N];
int n, k;

bool check(int x) {
	mem(sum, 0);
	for (int i = 1; i <= n; ++i) {
		if (bb[i] >= x) {
            //这里注意!一定是原数组和x去比较而非排序后的数组
			sum[i] = sum[i - 1] + 1;
		} else
			sum[i] = sum[i - 1] - 1;
	}
	int mi = 0;
	for (int i = k; i <= n; ++i) {
		if (sum[i] - mi > 0)
			return 1;
		mi = min(mi, sum[i - k + 1]);
            //贪心,sum[i] - mi尽量大即mi尽量小
	}
	return 0;
}

void work() {
	cin >> n >> k;
	for (int i = 1; i <= n; ++i) {
		cin >> arr[i];
		bb[i] = arr[i];
	}
	sort(arr + 1, arr + n + 1);
	int l = 1, r = n;
	while (r >= l) {
		int mid = (l + r) / 2;
		if (check(arr[mid])) {
			l = mid + 1;
		} else {
			r = mid - 1;
		}
	}
	cout << arr[l - 1] << '\n';//注意二分端点
}


signed main() {
	io;
	work();
	return 0;
}

 L.D - Dance (atcoder.jp)

思路:

回溯dfs爆搜,写dfs需要很细心

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define m_p make_pair
#define mem(a,b) memset(a,b,sizeof a)
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 3e5 + 50;
int arr[20][20];
bool vis[N];
int ans = -1;
int n;

void dfs(int cur, int val) {
//说明在2*n之前的所有数都已经配对了达到要求
	if (cur >= 2 * n) {
		ans = max(ans, val);
		return;
	}
//说明在搜到cur之前他已经被配对了
	if (vis[cur]) {
		dfs(cur + 1, val);
		return;
	}
//遍历cur之后的所有元素和cur配对,分别去搜cur+1
	for (int i = cur + 1; i <= 2 * n; ++i) {
		if (!vis[i]) {
			vis[i] = 1;
			dfs(cur + 1, val ^ arr[cur][i]);
			vis[i] = 0;
		}
	}
}

void work() {
	cin >> n;
	for (int i = 1; i < 2 * n; ++i) {
		for (int j = i + 1; j <= 2 * n; ++j) {
			cin >> arr[i][j];
		}
	}
	dfs(1, 0);
	cout << ans << '\n';
}

signed main() {
	io;
	work();
	return 0;
}

M.SPOJ.com - Problem DCEPC206

思路:

会F了这题不是一眼切?

但是树状数组卡常我是真的蚌埠住了(卡lowbit?),不知道线段树会不会好一些(线段树建树似乎比树状数组快不少,而且我怀疑以这个卡法线段树才是正解)

一些位运算buff:

a-=lowbit(a)  ==>   a = (a & (a + 1)) - 1)

a+=lowbit(a)   ==>   a = (a | (a + 1))

#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
//#define int ll
#define pb push_back
#define m_p make_pair
#define mem(a,b) memset(a,b,sizeof a)
//#define mod 998244353
#define inf 0x3f3f3f3f3f3f3f3f
const int N = 1e6 + 5;
ll sum[N];//小于arr[k]的数和
ll num;

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

inline ll getnum2(int a) {
	num = 0;
	for (; a >= 0; a = (a & (a + 1)) - 1) {
		num += sum[a];
	}
	return num;
}

inline void update2(int a, int t) {
	for (; a < N; a = (a | (a + 1))) {
		sum[a] += t;
	}
}


int main() {
	//io;
	int t;
	scanf("%d", &t);
	while (t--) {
		int n;
		scanf("%d", &n);
		mem(sum, 0);
		ll ans = 0;
		for (int i = 1; i <= n; ++i) {
			int a;
			scanf("%d", &a);
			update2(a, a);
			ans += getnum2(a - 1);
		}
		printf("%lld\n", ans);
	}
	return 0;
}

未完待续:航空管制

PS:感觉我的思维快要枯竭了怎么办

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值