20230603 一些小小的思维题

CF97B Superset
根据 104 的输入和 2105 的输出范围限制 考虑到可能是 nlogn 的做法
然后就有个很玄学的思路:平面分治
考虑分治的特点 即“将一个区间分成左右两半 并对于任意左半边的点 右半边的所有点对于该点都满足条件”
那么对于此题 考虑首先对于横坐标分成两半 那么对于任意左半边的点 右半边的所有点都满足 superset
那么显然 当中间那条线上有与这两点纵坐标相同的点 那必定满足上述条件
那么进一步想 实际上我们只需要在中间那条线上构造出区间内所有点的纵坐标相同的点(即区间内所有点向这条线横着投影) 便可满足此条件
那么每次加的点就是 r - l + 1
那就是 nlogn
code:

#include <bits/stdc++.h>
#define mid (l + r >> 1) 
using namespace std;

const int N = 2e5 + 0721;
struct node {
	int x, y;
	friend bool operator < (node a, node b) {
		if (a.x != b.x) return a.x < b.x;
		else return a.y < b.y;
	}
	friend bool operator == (node a, node b) {
		return a.x == b.x && a.y == b.y;
	}
} ori[N], ans[N];
int n, cnt;

void solve(int l, int r) {
	if (l >= r) return;
	int xx = ori[mid].x;
	for (int i = l; i <= r; ++i) {
		ans[++cnt].x = xx;
		ans[cnt].y = ori[i].y;
	}
	solve(l, mid - 1);
	solve(mid + 1, r);
}

void copy() {
	for (int i = 1; i <= n; ++i) {
		ans[++cnt].x = ori[i].x;
		ans[cnt].y = ori[i].y;
	}
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d%d", &ori[i].x, &ori[i].y);
	sort(ori + 1, ori + 1 + n);
	solve(1, n);
	copy();
	sort(ans + 1, ans + 1 + cnt);
	int k = unique(ans + 1, ans + 1 + cnt) - ans - 1;
	printf("%d\n",k);
	for (int i = 1; i <= k; ++i) printf("%d %d\n",ans[i].x, ans[i].y);
	
	return 0;
}

CF1220D Alex and Julian
判二分图 想到染色或者判奇环
对于此题 染色显然不太可做 那么我们思考怎么判奇环
进而思考怎样能构成一个环 考虑对于任何一个点和集合里任何两个数 x y 每多 x 连一条边 每多 y 连一条边
那么一定会有一个公共点在离该点 lcm(x,y) 的地方 那就构成了一个环
那这个环的长度就是 lcm(x,y)x+lcm(x,y)y
转化一下就是 ygcd(x,y)+xgcd(x,y)
要让这玩意是个偶数
我们考虑将x y 质因数分解 一定是若干个 2 和一坨奇数相乘
那么当 x 与 y 中含 2 的个数不相等时 一定有一个除完 gcd 还有若干个2和一坨奇数 另一个除完 gcd 只剩一坨奇数了
显然此时结果就是奇 + 偶 = 奇
当 x 与 y 中含 2 的个数相等时 两个除完 gcd 都只剩一坨奇数 那么结果就是 奇 + 奇 = 偶 满足条件
又因为 x y 是我们从 B 集合中随机选取的两个数 所以当且仅当 B 集合中所有数含 2 的个数相同时 才能构成一个二分图
那么我们把所给的数按照含 2 的个数分成若干组 选取含元素数量最多的那组就行了
code:

#include <bits/stdc++.h>
#define ll long long
using namespace std;

const int N = 2e5 + 0721;
int siz[64];
ll a[N];
bool vis[N];
int loc[N];
int n;

int count(ll x) {
	int ret = 0;
	while (((x & 1) == 0) && x != 0) {
		++ret;
		x >>= 1;
	}
	return ret;
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) {
		ll x;
		scanf("%lld", &x);
		a[i] = x;
		int wei = count(x);
		++siz[wei];
		loc[i] = wei;
	}
	
	int maxn = 0, maxid;
	for (int i = 0; i < 64; ++i) {
		if (siz[i] > maxn) maxn = siz[i], maxid = i;
	}
	
	printf("%d\n",n - maxn);
	for (int i = 1; i <= n; ++i) {
		if (loc[i] != maxid) printf("%lld ",a[i]);
	}
	
	return 0;
}

CF475B Strongly Connected City
保证联通 实际对每个点暴力 dfs 一遍就能过 复杂度 O(n2m2) 乱过
还有一种很巧的做法 因为中间的点都可以到达一条边界 那么如果所有边界互相联通 图里所有点就都联通了 O(1) 很好
当然看到这个题目名 第一反应还是 tarjan 考虑到好久没写过了 就写了个练练手 O(nm)
code:

#include <bits/stdc++.h>
using namespace std;

const int N = 2e6 + 0721;
int head[N], nxt[N], to[N], cnt;
int dfn[N], low[N], dfs_clock;
int sccnum[N], sccsz[N], scccnt;
int q[N], top;
int n, m;

inline void cmb(int x, int y) {
	to[++cnt] = y;
	nxt[cnt] = head[x];
	head[x] = cnt;
}

void tarjan(int x) {
	dfn[x] = low[x] = ++dfs_clock;
	q[++top] = x;
	for (int i = head[x]; i; i = nxt[i]) {
		int y = to[i];
		if (!dfn[y]) {
			tarjan(y);
			low[x] = min(low[x], low[y]);
		} else if (!sccnum[y])
			low[x] = min(low[x], dfn[y]);
	}
	if (dfn[x] == low[x]) {
		++scccnt;
		while (1) {
			int now = q[top];
			sccnum[now] = scccnt;
			--top;
			++sccsz[scccnt];
			if (now == x) break;
		}
	}
}

int main() {
	scanf("%d%d", &n, &m);
	string s1, s2;
	cin >> s1;
	for (int i = 1; i <= n; ++i) {
		int base = (i - 1) * m;
		for (int j = 1; j < m; ++j) {
			if (s1[i - 1] == '>') cmb(base + j, base + j + 1);
			else cmb(base + j + 1, base + j);
		}
	}
	cin >> s2;
	for (int j = 1; j <= m; ++j) {
		for (int i = 1; i < n; ++i) {
			if (s2[j - 1] == 'v') cmb(j + (i - 1) * m, j + i * m);
			else cmb(j + i * m, j + (i - 1) * m);
		}
	}
	
	tarjan(1);
	
	if (sccsz[1] == m * n) printf("YES");
	else printf("NO");
	
	return 0;
}

CF437C The Child and Toy
为什么这题是个蓝啊
考虑删除对答案的贡献 发现每次删除对于点的贡献是会变的 不太好做
然后我们发现实际上点全删没了边也都删没了 于是想到把删点转化为删边
那我们考虑每条边 其最优贡献一定是两边中较小的那个点
于是我们要尽可能要让那个较大的点在那个较小的点之前删掉
然后我们发现 如果从大到小删点 一定能让每条边都能取到最优贡献
然后直接算就行了
code:

#include <bits/stdc++.h>
#define ll long long
using namespace std;

const int N = 0x0d00;
int val[N];
int n, m;
ll ans;

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; ++i) scanf("%d", &val[i]);
	for (int i = 1; i <= m; ++i) {
		int x, y;
		scanf("%d%d", &x, &y);
		ans += min(val[x], val[y]);
	}
	printf("%lld",ans);
	
	return 0;
}

CF295B Greg and Graph
发现删点不太好做 想到倒着做
那我们考虑每次加一个点对于答案的影响 显然对于一对点 i j 有一条新路径叫 dis[i][k] + dis[k][j]
就非常的像 floyd
然后就做完了 O(n3)
code:

#include <bits/stdc++.h>
#define ll long long
using namespace std;

const int N = 521;
ll ans[N], dis[N][N];
bool vis[N];
int del[N];
int n;

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= n; ++j) scanf("%lld", &dis[i][j]);
	}
	for (int i = 1; i <= n; ++i) scanf("%d", &del[i]);
	
	for (int ii = n; ii >= 1; --ii) {
		vis[del[ii]] = 1;
		for (int i = 1; i <= n; ++i) {
			for (int j = 1; j <= n; ++j) {
				dis[i][j] = min(dis[i][j], dis[i][del[ii]] + dis[del[ii]][j]);
			}
		}
		for (int i = 1; i <= n; ++i) {
			for (int j = 1; j <= n; ++j) {
				if (!vis[i] || !vis[j]) continue;
				ans[ii] += dis[i][j];
			}
		}
	}
	
	for (int i = 1; i <= n; ++i) printf("%lld ",ans[i]);
	
	return 0;
}

CF1214D Treasure Island
还是瞪眼找性质 发现最多只需要堵两个点
那么答案就是 0 1 2 中的一个 就可以暴力分讨了
首先不联通答案肯定是 0
那么我们思考一下怎么判 1 和 2
发现如果所有路都经过一个共同点 那我们把这个点堵上答案就是 1
否则答案就是 2
当然做到这可以建图求割点 但是还有一种更巧的做法:
我们 dfs 出一条路径所经过的点 然后把这条路径上的所有点都堵上 因为如果存在 1 的点肯定会在这条路径上
然后再搜一遍看联不联通就行
需要注意爆搜的时候需要一个类似于 vis 数组的东西防止多次走进死胡同

#include <bits/stdc++.h>
using namespace std;

const int N = 1e6 + 0721;
string s[N];
struct node {
	int x, y;
} q[N];
int dx[2] = {0, 1};
int dy[2] = {1, 0};
int top;
int n, m;
bool flag;

inline bool exist(int x, int y) {
	return x > 0 && x <= n && y > 0 && y <= m && s[x][y] != '#';
}

void dfs(int x, int y) {
	if (flag) return;
    if (x == n && y == m)
    {
        flag = 1;
        return;
    }
    for (int i = 0 ; i < 2; ++i)
    {
        int tox = x + dx[i], toy = y + dy[i];
        if (!exist(tox, toy)) continue;
        s[tox][toy]='#';
        dfs(tox ,toy);
        if(flag) return ;
    }
}

int main() {
	ios::sync_with_stdio(false);
	cin >> n >> m;
	for (int i = 1; i <= n; ++i) {
		cin >> s[i];
		s[i] = '?' + s[i];
	}
	
	dfs(1, 1);
	if (!flag) {
		cout << '0';
		return 0;
	}

	flag = 0;
	s[n][m] = '.';
	dfs(1, 1);
//	for (int i = 1; i <= top; ++i) cout << q[i].x << ' ' << q[i].y << '\n';
	if (flag) cout << '2';
	else cout << '1';
	
	return 0;
}

P1073 [NOIP2009 提高组] 最优贸易
第一眼分层图最短路 但是有负权边要跑 spfa 可能会被卡掉 结果这题没卡
然后发现这题最多只能买卖一次 所以我们对于一个点 我们找到在这个点之前买入的最低价格和卖出的最高价格
然后一减就是对于这个点的答案 我们把所有点的这个答案算一遍统计最大值就行
转移的时候可以用一个类似于 dijkstra 的方法转移
code:

#include <bits/stdc++.h>
using namespace std;

const int N = 5e5 + 0721;
int head1[N], nxt1[N], to1[N], cnt1;
int head2[N], nxt2[N], to2[N], cnt2;
int dis1[N], dis2[N], v[N];
bool vis1[N], vis2[N];
int n, m;

void cmb1(int x, int y) {
    to1[++cnt1] = y;
    nxt1[cnt1] = head1[x];
    head1[x] = cnt1;
}

void cmb2(int x, int y) {
    to2[++cnt2] = y;
    nxt2[cnt2] = head2[x];
    head2[x] = cnt2;
}

struct node1 {
    int id, dis;
    friend bool operator<(node1 a, node1 b) { return a.dis > b.dis; }
};
priority_queue<node1> q1;

inline void dijkstra1(int s) {
    memset(dis1, 0x3f, sizeof(dis1));
    dis1[s] = v[s];
    q1.push((node1){ s, dis1[s] });
    while (!q1.empty()) {
        int now = q1.top().id;
        q1.pop();
        if (vis1[now])
            continue;
        vis1[now] = 1;
        for (int i = head1[now]; i; i = nxt1[i]) {
            int y = to1[i];
            if (dis1[y] > min(dis1[now], v[y])) {
                dis1[y] = min(dis1[now], v[y]);
                q1.push((node1){ y, dis1[y] });
            }
        }
    }
}

struct node2 {
    int id, dis;
    friend bool operator<(node2 a, node2 b) { return a.dis < b.dis; }
};
priority_queue<node2> q2;

inline void dijkstra2(int s) {
    memset(dis2, -0x3f, sizeof(dis2));
    dis2[s] = v[s];
    q2.push((node2){ s, dis2[s] });
    while (!q2.empty()) {
        int now = q2.top().id;
        q2.pop();
        if (vis2[now])
            continue;
        vis2[now] = 1;
        for (int i = head2[now]; i; i = nxt2[i]) {
            int y = to2[i];
            if (dis2[y] < max(dis2[now], v[y])) {
                dis2[y] = max(dis2[now], v[y]);
                q2.push((node2){ y, dis2[y] });
            }
        }
    }
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i) scanf("%d", &v[i]);
    for (int i = 1; i <= m; ++i) {
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        if (z == 1) {
            cmb1(x, y);
            cmb2(y, x);
        } else {
            cmb1(x, y);
            cmb2(y, x);
            cmb1(y, x);
            cmb2(x, y);
        }
    }

    dijkstra1(1);
    dijkstra2(n);

    int ans = 0;
    for (int i = 1; i <= n; ++i) ans = max(ans, dis2[i] - dis1[i]);

    printf("%d", ans);

    return 0;
}
posted @   Steven24  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示