大一下第五周学习笔记

这周继续坚持早睡早起和晨跑,尤其11点左右睡,加油

 

周一 3.29(杂题)

发现cf的题真的质量挺高,能学到挺多东西的

以后每周自己去vjudge弄比赛,拉四五道比我水平高一些的题目,比如目前就是cf 1800~1900的题目,然后赛后补题

这样训练效果是最好的,都是比自己水平高一点的题目,不做太难的题也不做太简单的题,同时又有比赛的感觉

B. Playlist(链表 + set)

这题其实思路挺简单,就看你怎么实现这个东西

用链表维护原来的数,用set维护要删掉的数

但是我写的很复杂,调试来去心态爆炸,其实我已经完成了百分之80了

后来我看了题解,以及rank1的代码,真的很优美,以后做cf题多看rank1的代码

发现他在set中用了一个upper_bound大大简化了代码,而我只会用find

还有发现他很喜欢打空行分割代码,我也可以多学习

#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 = 1e5 + 10;
int a[N], pre[N], nex[N], n;
vector<int> ans;
set<int> s;

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

int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		scanf("%d", &n);
		_for(i, 1, n)
		{
			scanf("%d", &a[i]);
			if(i == 1) pre[1] = n; else pre[i] = i - 1;
			if(i == n) nex[n] = 1; else nex[i] = i + 1;
		}
		
		s.clear();
		_for(i, 1, n)
			if(gcd(a[pre[i]], a[i]) == 1)
				s.insert(i);
		
		ans.clear();
		int now = 1;
		while(!s.empty())
		{
			auto it = s.upper_bound(now);
			if(it == s.end()) it = s.begin();
			now = *it;
			ans.push_back(now);
			
			if(pre[now] == now) break;
			nex[pre[now]] = nex[now];
			pre[nex[now]] = pre[now];
			
			s.erase(now); 
			s.erase(nex[now]);
			
			if(gcd(a[pre[now]], a[nex[now]]) == 1) s.insert(nex[now]);
			now = nex[now];
		}
		
		printf("%d ", ans.size());
		for(int i: ans) printf("%d ", i);
		puts("");
	}
	
	return 0;
}

周三 3.31 (杂题 + Tarjan)

昨天状态比较差,很困,就没怎么学

体能还是非常重要的,坚持早睡早起和晨跑,维持良好的体能

C. Skyline Photo(dp + 单调栈)

这是一道2100的dp,挺难的,也学到了很多

自己想只想到了很显然O(n^2)的dp

dp[i]表示1到i的最优值,显然枚举i的照片有多长就可以求出

但是没想到怎么优化

看了题解

突破口在于观察到一个性质,观察到这个性质后面就迎刃而解了

当前求dp[i]

那么设最近的小于hi的点在j

那么照片从j到i的时候,这张照片的值就是bj

照片再长的时候,就可以转化成dpj的情况

照片更长时的最优值已经保存在dpj了

因此求dpi可以分为两部分

第一部分是dp[j]

第二部分是照片比较短,还没有到j的情况

第二部分答案就是b[i] + max(dp[j……i-1])

考虑怎么实现

首先找j用到了单调栈,用一个单调栈维护就好

其次dp[j……i-1]的最大值可以用线段树维护

因此复杂度是O(nlogn)的

但是还可以再优化,不用到线段树

再维护单调栈的过程中统计弹出元素的dp值

但是之前已经删掉了一些元素,也就统计不到它们的dp值

那怎么办,我们可以用k数组,把之前删掉元素的dp值保存在里面

也就是k[i]为dp[i]以及加入i时删掉的元素的dp值得最大值

这样就可以O(n)做出这道题目

这里还有一个坑就是可能找不到j,也就是左边得元素全部比i大

那么这时答案就是b[i] + max(所有删除的k)

但是注意可以全部为一张照片,也就是max(max(所有删除的k), 0)

这个地方我WA了一发

这道题还是很精彩的

核心是观察出那个性质,我写的时候没观察出来

O(n)做法

#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 h[N], v[N], n;
ll dp[N], k[N];
stack<int> s;

int main()
{
	scanf("%d", &n);
	_for(i, 1, n) scanf("%d", &h[i]);
	_for(i, 1, n) scanf("%d", &v[i]);
	
	dp[1] = v[1];
	s.push(1);
	k[1] = dp[1];
	
	_for(i, 2, n)
	{
		ll mx = -1e18;
		while(!s.empty() && h[s.top()] > h[i])
		{
			mx = max(mx, k[s.top()]);
			s.pop();
		}
		
		ll t = mx;
		if(s.empty()) dp[i] = v[i] + max(mx, 0ll);
		else
		{
			mx = max(mx, dp[s.top()]);
			dp[i] = max(v[i] + mx, dp[s.top()]);
		}
		s.push(i);
		k[i] = max(dp[i], t);
	} 
	printf("%lld\n", dp[n]);
	
	return 0;
}

O(nlogn)线段树做法

理解线段树的本质,按照实际需要打线段树

不用结构体写挺好的

#include<bits/stdc++.h>
#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 = 3e5 + 10;
int h[N], v[N], n;
ll dp[N], t[N << 2];
stack<int> s;

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

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

ll query(int k, int l, int r, int L, int R)
{
	if(L <= l && r <= R) return t[k];
	ll res = -1e18;
	int m = (l + r) >> 1;
	if(L <= m) res = max(res, query(l(k), l, m, L, R));
	if(R > m) res = max(res, query(r(k), m + 1, r, L, R));
	return res;
}
 
int main()
{
	scanf("%d", &n);
	_for(i, 1, n) scanf("%d", &h[i]);
	_for(i, 1, n) scanf("%d", &v[i]);
	
	dp[1] = v[1];
	s.push(1);
	change(1, 1, n, 1, dp[1]);
	
	_for(i, 2, n)
	{
		while(!s.empty() && h[s.top()] > h[i]) s.pop();
		
		if(s.empty()) dp[i] = v[i] + max(query(1, 1, n, 1, i - 1), 0ll);
		else dp[i] = max(v[i] + query(1, 1, n, s.top(), i - 1), dp[s.top()]);
		
		change(1, 1, n, i, dp[i]);
		s.push(i);
	} 
	printf("%lld\n", dp[n]);
	
	return 0;
}

P3388 无向图割点

理解算法本质。也许几个月后你都不记得具体怎么实现的,但你理解了本质,就可以依靠理解写出代码

Tarjan的核心是dfs序和low数组

一个点祖先的dfs序一定比它小,先搜到

根据这个判断所有后代中有没有边连到祖先

那么就要求low数组,所有后代往上连最上能到多少

根节点要额外处理一下,无向图没有横叉边,所以根节点有2个以上子树时,没了根节点子树就不连通了

#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 = 2e4 + 10;
vector<int> g[N];
int dfn[N], low[N], ans[N], n, m, root, cnt;

void dfs(int u)
{
	dfn[u] = low[u] = ++cnt; //low初始化 
	int son = 0; 
	for(int v: g[u])
	{
		if(!dfn[v])
		{
			dfs(v); //先搜,回溯时再统计。也避免了叶子节点为割点 
			low[u] = min(low[u], low[v]);
			if(u == root && ++son > 1) ans[u] = 1; //注意不是原图上连的边,是搜索树上有多少子树 
			if(u != root && low[v] >= dfn[u]) ans[u] = 1; //有一个儿子符合就是割点 
		}
		else low[u] = min(low[u], dfn[v]); //注意这里是 dfn[v] 不是low[v] 
	}
}
 
int main()
{
	scanf("%d%d", &n, &m);
	while(m--)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		g[u].push_back(v);
		g[v].push_back(u);
	}
	
	_for(i, 1, n)
		if(!dfn[i]) //防止图不连通 
			dfs(root = i);
			
	int sum = 0;
	_for(i, 1, n)
		if(ans[i])
			sum++;
	
	printf("%d\n", sum);
	_for(i, 1, n)
		if(ans[i])
			printf("%d ", i); 
	
	return 0;
}

「一本通 3.5 例 1」受欢迎的牛(缩点模板题)

这题具有传递性,因此可以缩点

变成一个有向无环图

按照题意每个点都可以达到所求的点,此时这个点不能向外连边,否则出现环,而这个图是有向无环图

但是如果有两个以上出度为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;
 
const int N = 1e4 + 10;
vector<int> g[N];
int st[N], dfn[N], low[N], num[N], co[N], out[N];
int id, cnt, top, n, m;

void dfs(int u)
{
	dfn[u] = low[u] = ++cnt;
	st[++top] = u; //新搜到的点入栈 
	
	for(int v: g[u])
	{
		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;
			num[id]++;
			top--;
			if(st[top + 1] == u) break; //在栈中从栈顶到u属于一个联通分量 
		}
	}
}

int main()
{
	scanf("%d%d", &n, &m);
	while(m--)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		g[u].push_back(v);
	}
	
	_for(i, 1, n)
		if(!dfn[i]) //图可能不是联通的 
			dfs(i);
	
	_for(u, 1, n)
		for(int v: g[u])
			if(co[u] != co[v]) //属于不同强连通的边 
				out[co[u]]++;

	int sum = 0, ans;		
	_for(i, 1, id) //枚举每一个强联通 
		if(!out[i])	
		{
			if(++sum > 1)
			{
				puts("0");
				return 0;
			}
			ans = num[i];
		}
	printf("%d\n", ans);

	return 0;
}

周四 4.1 (强连通分量)

「一本通 3.5 例 2」最大半连通子图(缩点 + 拓扑排序 + dp)

这类问题是缩点dp,第一次做

缩点的作用就是把有环的图变成有向无环图

没有环之后就有一些新的性质,比如存在拓扑序,可以dp

这道题按照题意缩点之后就变成了最长链,因为不存在环。

然后在DAG上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;

const int N = 1e5 + 10;
vector<int> g[N], G[N];
int a[N], b[N], dfn[N], low[N], st[N], co[N];
int dis[N], num[N], in[N], dp[N];
int n, m, x, cnt, top, id, ans;
vector<pair<int, int> > Edge;

void dfs(int u) //缩点 
{
	dfn[u] = low[u] = ++cnt;
	st[++top] = u;
	
	for(int v: g[u])
	{
		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;
			num[id]++;
			if(st[top + 1] == u) break;
		}
	}
}

void build() //建立新图, 点1到id G存边 
{
	_for(i, 1, n)
		if(!dfn[i])
			dfs(i);
	
	_for(u, 1, n) 
		for(int v: g[u])
			if(co[u] != co[v])
				Edge.push_back(make_pair(co[u], co[v]));
	
	sort(Edge.begin(), Edge.end());
	int t = unique(Edge.begin(), Edge.end()) - Edge.begin(); //缩点后有很多重边,去重 
	REP(i, 0, t)
	{
		int u = Edge[i].first, v = Edge[i].second;
		G[u].push_back(v);
		in[v]++;
	}
}

void topo() //边topo边dp 
{
	queue<int> q;
	_for(i, 1, id)
		if(!in[i])
		{
			q.push(i);
			dp[i] = 1;
			dis[i] = num[i];
			ans = max(ans, dis[i]); //初始化也要更新答案,小心坑。多写一点周全一点 
		}	
			
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		for(int v: G[u])
		{
			if(--in[v] == 0) q.push(v);
			if(dis[v] < dis[u] + num[v])
			{
				dis[v] = dis[u] + num[v];
				dp[v] = dp[u]; //注意不是为1 
				ans = max(ans, dis[v]);
			}
			else if(dis[v] == dis[u] + num[v]) //不去重边的话这个地方会多算 
				dp[v] = (dp[v] + dp[u]) % x;
		}
	}
}

int main()
{
	scanf("%d%d%d", &n, &m, &x);
	while(m--)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		g[u].push_back(v);
	}
	
	build();
	topo();
	
	int sum = 0;
	_for(i, 1, id)
		if(dis[i] == ans)
			sum = (sum + dp[i]) % x;
	printf("%d\n%d\n", ans, sum);
	
	return 0;
}

周五 4.2 (强连通分量)

P3387 【模板】缩点

这题比昨天那题简单

注意几个点
一.如果要去重的话,unique返回的是end的值
二.如果有重边,则路径条数会多算,是错误的。这道题只是求点权和最大的路径,没有求条数,所以可以不用去重 

#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 = 1e4 + 10;
int a[N], val[N], dfn[N], low[N], co[N], st[N], in[N], dis[N];
int top, id, cnt, n, m;
vector<int> g[N], G[N];
vector<pair<int, int> > Edge;

void dfs(int u)
{
	dfn[u] = low[u] = ++cnt;
	st[++top] = u;
	
	for(int v: g[u])
	{
		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;
			val[id] += a[st[top]];
			top--;
			if(st[top + 1] == u) break;
		}
	}
}

void build()
{
	_for(i, 1, n)
		if(!dfn[i])
			dfs(i);
	
	_for(u, 1, n)
		for(int v: g[u])
			if(co[u] != co[v])
			{
				G[co[u]].push_back(co[v]);
				in[co[v]]++;
			}
}

void topo()
{
	queue<int> q;
	_for(i, 1, id)
		if(!in[i])
		{
			q.push(i);
			dis[i] = val[i];
		}
	
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		for(int v: G[u])
		{
			if(--in[v] == 0) q.push(v);
			dis[v] = max(dis[v], dis[u] + val[v]);
		}
	}
}

int main()
{
	scanf("%d%d", &n, &m);
	_for(i, 1, n) scanf("%d", &a[i]);
	while(m--)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		g[u].push_back(v);
	}
	
	build();
	topo();
	
	int ans = 0;
	_for(i, 1, id)
		ans = max(ans, dis[i]);
	printf("%d\n", ans);
	
	return 0;
}

周六 4.3(强连通分量)

最近欠了挺多作业,所以训练量会少一些

「一本通 3.5 练习 2」消息的传递(缩点 + 拓扑排序)

首先可以互相传递,一个强连通分量内可以相互传递,所以可以等价为一个点

然后就变成了有向无环图,可以拓扑排序

如果要遍历所有点,就要从所有入度为0的点开始遍历,有向无环图存在拓扑序,所以最后一定可以遍历完整个图

所以答案就是缩点之后入度为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;

const int N = 1e3 + 10;
vector<int> g[N];
int dfn[N], low[N], co[N], st[N], in[N];
int top, cnt, n, id;

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

void build()
{
	_for(i, 1, n)
		if(!dfn[i])
			dfs(i);
	
	_for(u, 1, n)
		for(int v: g[u])
			if(co[u] != co[v])
				in[co[v]]++;
}

int main()
{
	scanf("%d", &n);
	_for(i, 1, n)
		_for(j, 1, n)
		{
			int x; scanf("%d", &x);
			if(x) g[i].push_back(j);
		}
		
	build();
	
	int sum = 0; 
	_for(i, 1, id)	
		if(!in[i])
			sum++;
	printf("%d\n", sum);
	
	return 0;
}

「一本通 3.5 练习 4」抢掠计划(缩点 + dp)

和洛谷的模板缩点挺像

这题不一样的地方在于固定了最长链的起点

而洛谷那题没有固定起点

那这个时候就不用拓扑排序了

从起点开始spfa就好

spfa稍微改一下

边权改为点权,求最小改为求最大

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;

const int N = 5e5 + 10;
vector<int> g[N], G[N];
int dfn[N], low[N], st[N], a[N], k[N], co[N], val[N], vis[N], dp[N];
int n, m, top, s, p, id, cnt;

void dfs(int u)
{
	dfn[u] = low[u] = ++cnt;
	st[++top] = u;
	
	for(int v: g[u])
	{
		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;
			val[id] += a[st[top]];
			top--;
			if(st[top + 1] == u) break;
		}
	}
}

void build()
{
	_for(i, 1, n)
		if(!dfn[i])
			dfs(i);
	
	_for(u, 1, n)
		for(int v: g[u])
			if(co[u] != co[v])
				G[co[u]].push_back(co[v]);
}

void work()
{
	queue<int> q;
	q.push(co[s]);
	dp[co[s]] = val[co[s]];
	vis[co[s]] = 1;
	
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		vis[u] = 0;
		for(int v: G[u])
			if(dp[v] < dp[u] + val[v])
			{
				dp[v] = dp[u] + val[v];
				if(!vis[v]) q.push(v);
			}
	}
}

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

	build();
	work();
	
	int ans = 0;
	_for(i, 1, p)
		ans = max(ans, dp[co[k[i]]]);
	printf("%d\n", ans);
	
	return 0;
}

「一本通 3.5 练习 3」间谍网络(缩点)

和前面的题很像

同样考虑入度为0的点就行了

如果不能控制所有间谍的话,那就dfs一下,把所有能经过的点都标记一下就行了

#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 = 3e3 + 10;
vector<int> g[N], G[N];
int dfn[N], low[N], co[N], st[N], in[N], a[N], val[N], vis[N];
int top, cnt, n, id, p, m;

void dfs(int u)
{
	dfn[u] = low[u] = ++cnt;
	st[++top] = u;
	
	for(int v: g[u])
	{
		if(!dfn[v])
		{
			dfs(v);
			low[u] = min(low[u], low[v]);
		}
		else if(!co[v]) low[u] = min(low[u], dfn[v]);
	}
	
	if(dfn[u] == low[u])
	{
		id++;
		val[id] = 1e9;
		while(1)
		{
			co[st[top]] = id;
			val[id] = min(val[id], a[st[top]]);
			top--;
			if(st[top + 1] == u) break;
		}
	}
}

void build()
{
	_for(i, 1, n)
		if(!dfn[i])
			dfs(i);
	
	_for(u, 1, n)
		for(int v: g[u])
			if(co[u] != co[v])
			{
				in[co[v]]++;
				G[co[u]].push_back(co[v]);
			}
}

void search(int u)
{
	vis[u] = 1;
	for(int v: G[u])
		if(!vis[v])
			search(v);
}

int main()
{
	scanf("%d%d", &n, &p);
	_for(i, 1, n) a[i] = 1e9;
	while(p--)
	{
		int i, j;
		scanf("%d%d", &i, &j);
		a[i] = j;	
	}
	scanf("%d", &m);
	while(m--)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		g[u].push_back(v);
	}
	
	build();
	
	int ans = 0, flag = 0;
	_for(i, 1, id)
		if(!in[i])
		{
			if(val[i] == 1e9)
			{
				flag = 1;
				break;
			}
			ans += val[i];
		}
	
	if(!flag) 
	{
		printf("YES\n%d\n", ans);
		return 0;
	}
	
	_for(i, 1, id)
		if(val[i] != 1e9 && !vis[i])
			search(i);
	
	puts("NO");
	_for(i, 1, n)
		if(!vis[co[i]])
		{
			printf("%d\n", i);
			break;
		}
	
	return 0;
}

「一本通 3.5 练习 1」网络协议(缩点 + 判断度数猜结论)

我靠自己写写到了92分,一个点过不去。

我的构造方法应该比较接近正解了,但是肯定有什么地方没考虑到

刚了好久,最后看了题解

发现答案很简单,就是max(入度为0的点,出度为0的点)

从度数的角度考虑,强连通是不存在入度和出度为0的点的

所以把出度为0的点向入度为0的点连边,就不存在出入度为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;

const int N = 1e2 + 10;
vector<int> g[N];
int dfn[N], low[N], co[N], st[N], in[N], out[N];
int top, cnt, n, id;

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

void build()
{
	_for(i, 1, n)
		if(!dfn[i])
			dfs(i);
	
	_for(u, 1, n)
		for(int v: g[u])
			if(co[u] != co[v])
				out[co[u]]++, in[co[v]]++;
}

int main()
{
	scanf("%d", &n);
	_for(i, 1, n)
	{
		int x; 
		while(scanf("%d", &x) && x)
			g[i].push_back(x);
	}
	
	build();
	
	int sum0 = 0, sum1 = 0;
	_for(i, 1, id)	
	{
		if(!in[i]) sum0++;
		if(!out[i]) sum1++;
	}
	
	if(id == 1) printf("1\n0\n");
	else printf("%d\n%d\n", sum0, max(sum0, sum1));
		
	return 0;
}

周日 4.4 (cf补题)

「一本通 3.5 练习 5」和平委员会,想了挺久没有清晰的做法,后来看题解看到是2-sat模板题

这个算法没学过……

以后会专门学这个算法的,先跳过

还好没有卡太久

做题的时候有充分思考还是想不出才看题解,把握好度,不要马上就看,也少死磕

有时候是因为有些知识点没学过

 

昨晚打了人生中第一场cf

之前因为不想熬夜所以一直没正式打,一般做题或者vp

这次div2 rank 1287

1个小时10分钟前三道题,后来D题卡住了,最后又跑去看E题,事实证明当时还是继续看D题比较好

心态不错,不着急,正常节奏写题思考,这样发挥是最好的

 

D. 3-Coloring(构造 + 套路)

这和之前一道cf题很像

也是构造一个矩阵,使得相邻的数不相同

那题的做法像国际象棋棋盘,按照奇偶

这题也一样,都是套路,我考试时没想起来这玩意

所以填成

1 2 1

2 1 2

1 2 1

这样就行了,能填1就填1,不能填就填2

然后1和2其中一个填完了,比如1填完了,要填2,

能填1填1,不能填就填3

视线上注意一下细节,比如跳到下一行

#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;

int n;

void print(int x, int y, int k)
{
	printf("%d %d %d\n", k, x, y);
	fflush(stdout);
}

void update(int& x, int& y)
{
	y += 2;
	if(y > n)
	{
		y = y % 2 + 1;
		x++;
	}
}
 
int main()
{
	scanf("%d", &n);
	int x1 = 1, y1 = 1, x2 = 1, y2 = 2;
	int x3, y3, p; 
	
	_for(i, 1, n * n)
	{
		int x; scanf("%d", &x);
		if(x == 2 || x == 3)
		{
			print(x1, y1, 1);
			if(x1 == n && y1 == n) 
			{
				p = 2;
				break;
			}
			update(x1, y1);
		}
		else
		{
			print(x2, y2, 2);
			if(x2 == n && y2 == n - 1)
			{
				p = 1;
				break;
			}
			update(x2, y2);
		}
	}
	
	if(p == 2) x3 = x2, y3 = y2;
	else x3 = x1, y3 = y1;
	
	while(1)
	{
		int x; scanf("%d", &x);
		if(x != 3) print(x3, y3, 3);
		else print(x3, y3, p);
		if(x3 == n && (y3 == n - 1 || y3 == n)) break;
		update(x3, y3);
	}
	
	return 0;
}

 

听学长讲想外出比赛需要超过一些学长

压力好大啊

突然感觉自己挺懈怠的

下一周搞起

绩点竞赛全都要,做好时间管理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码下载:完整代码,可直接运行 ;运行版本:2022a或2019b或2014a;若运行有问题,可私信博主; **仿真咨询 1 各类智能优化算法改进及应用** 生产调度、经济调度、装配线调度、充电优化、车间调度、发车优化、水库调度、三维装箱、物流选址、货位优化、公交排班优化、充电桩布局优化、车间布局优化、集装箱船配载优化、水泵组合优化、解医疗资源分配优化、设施布局优化、可视域基站和无人机选址优化 **2 机器学习和深度学习方面** 卷积神经网络(CNN)、LSTM、支持向量机(SVM)、最小二乘支持向量机(LSSVM)、极限学习机(ELM)、核极限学习机(KELM)、BP、RBF、宽度学习、DBN、RF、RBF、DELM、XGBOOST、TCN实现风电预测、光伏预测、电池寿命预测、辐射源识别、交通流预测、负荷预测、股价预测、PM2.5浓度预测、电池健康状态预测、水体光学参数反演、NLOS信号识别、地铁停车精准预测、变压器故障诊断 **3 图像处理方面** 图像识别、图像分割、图像检测、图像隐藏、图像配准、图像拼接、图像融合、图像增强、图像压缩感知 **4 路径规划方面** 旅行商问题(TSP)、车辆路径问题(VRP、MVRP、CVRP、VRPTW等)、无人机三维路径规划、无人机协同、无人机编队、机器人路径规划、栅格地图路径规划、多式联运运输问题、车辆协同无人机路径规划、天线线性阵列分布优化、车间布局优化 **5 无人机应用方面** 无人机路径规划、无人机控制、无人机编队、无人机协同、无人机任务分配 **6 无线传感器定位及布局方面** 传感器部署优化、通信协议优化、路由优化、目标定位优化、Dv-Hop定位优化、Leach协议优化、WSN覆盖优化、组播优化、RSSI定位优化 **7 信号处理方面** 信号识别、信号加密、信号去噪、信号增强、雷达信号处理、信号水印嵌入提取、肌电信号、脑电信号、信号配时优化 **8 电力系统方面** 微电网优化、无功优化、配电网重构、储能配置 **9 元胞自动机方面** 交通流 人群疏散 病毒扩散 晶体生长 **10 雷达方面** 卡尔曼滤波跟踪、航迹关联、航迹融合

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值