大一下第四周学习笔记

周一 3.22(杂题)

Hongcow Builds A Nation (并查集 + 思维)

其实这道题并不难

只是在比赛中为了快点做出,乱猜是贪心,然后思路错误浪费了大量时间,依然WA

所以比赛中静下心和平时一样想题目才是发挥地最好的

这题就是用并查集处理出几个联通分量,没有被标记的联通分量看作一个大联通分量

然后联通分量内部连边,然后未被标记的大联通分量和被标记联通分量里面个数最多的连边

最后减去原来的边就ok了

其实蛮简单的

#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;
int f[N], a[N], cnt[N], n, m, k;

int find(int x)
{
	if(f[x] == x) return x;
	return f[x] = find(f[x]);
}

int main()
{
	scanf("%d%d%d", &n, &m, &k);
	_for(i, 1, n) f[i] = i;
	while(k--)
	{
		int x; scanf("%d", &x);
		a[x] = 1;	
	} 
	_for(i, 1, m)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		f[find(u)] = find(v);
	}
	
	int sum = 0, mx = 0, t = 0;
	_for(i, 1, n)
	{
		if(a[i]) a[find(i)] = 1;
		cnt[find(i)]++;
	}
	_for(i, 1, n)
		if(f[i] == i && a[i])
		{
			sum += cnt[i] * (cnt[i] - 1) / 2;
			if(a[i]) mx = max(mx, cnt[i]);
		}	
	_for(i, 1, n)
		if(!a[find(i)])
			t++;
	sum += mx * t + t * (t - 1) / 2;
	printf("%d\n", sum - m);

	return 0;
}

Stairs and Elevators(二分查找)

这道题考试的时候都没读题,其实蛮简单的,二分一下就完事了

注意到这道题给的范围很大到1e8

所以给的坐标直接二分一下找到相邻的位置就好了

找到相邻的位置无非就四种可能,四种可能里面取最优就好了

注意可能并不需要走楼梯和电梯,当x相同的时候,要特判一下,我因为这个WA了一次

还有总是重复的量可以用变成存一下,代码会简洁很多,也容易调试

#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 n, m, l[N], e[N], cl, ce, v;

int main()
{
	scanf("%d%d%d%d%d", &n, &m, &cl, &ce, &v);
	_for(i, 1, cl) scanf("%d", &l[i]);
	_for(i, 1, ce) scanf("%d", &e[i]);
	
	int q; scanf("%d", &q);
	while(q--)
	{
		int ans = 1e9, x1, y1, x2, y2;
		scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
		if(y1 > y2) swap(x1, x2), swap(y1, y2);
		
		if(x1 == x2)
		{
			printf("%d\n", abs(y1 - y2));
			continue;
		}
		
		int dx = abs(x1 - x2), dy = abs(y1 - y2);
		int now = lower_bound(l + 1, l + cl + 1, y1) - l;
		if(now != cl + 1) ans = min(ans, dx + dy + 2 * max(l[now] - y2, 0));
		if(now - 1 >= 1) ans = min(ans, dx + dy +  2 * abs(y1 - l[now - 1]));
		
		now = lower_bound(e + 1, e + ce + 1, y1) - e;
		dx = (dx + v - 1) / v;
		if(now != ce + 1) ans = min(ans, dx + dy + 2 * max(e[now] - y2, 0));
		if(now - 1 >= 1) ans = min(ans, dx + dy + 2 * abs(y1 - e[now - 1]));
		
		printf("%d\n", ans);
	}

	return 0;
}

D. Timetable(分组背包 + 暴力)

这道题真的一言难尽

考试的时候一读完题就知道是分组背包

要预处理一下每一天翘i节课最多能少掉多少时间

结果我没怎么想清楚,感觉是贪心,然后就写了贪心

比赛时一定要和平时一样想清楚,静下心。不要随便贪心,这道题的贪心是错误的

赛后发现贪心是错误的,如果暴力求这个,复杂度理论上是500的三次方,会超时

然后我就想什么方法优化,然后一直想不出

然而最后发现不会超时,可能是因为1.25e8比较极限,没超得太远,而且数据也没那么强

我在这浪费了很多时间,一直以为会超时。有时候到1e8比较极限的时候也可以试一下,说不定数据水

#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 = 500 + 10;
struct node{ int w, v; };
vector<node> ve[N];
int f[N], L[N], R[N];
int n, m, k, sum;

int main()
{
	scanf("%d%d%d", &n, &m, &k);
	_for(j, 1, n)
	{
		int t = 0, cnt = 0, l; 
		_for(i, 1, m) 
		{
			int x; scanf("%1d", &x);
			if(!x) continue;
			if(!t) l = i;
			else L[++cnt] = i - t;	
			t = i;
		}	
		if(!t) continue;
		sum += t - l + 1;
		
		_for(i, 1, cnt) R[i] = L[cnt - i + 1];
		_for(i, 1, cnt) L[i] += L[i - 1], R[i] += R[i - 1];	 

		int w = 0, mx = 0;
		_for(p, 1, min(cnt, k))
		{
			mx = 0;
			_for(i, 0, p)
				mx = max(mx, L[i] + R[p - i]);
			ve[j].push_back(node{++w, mx});
		}
		ve[j].push_back(node{++w, mx + 1});
	}

	_for(i, 1, n) 
		for(int j = k; j >= 0; j--)
			REP(t, 0, ve[i].size())
			{
				int w = ve[i][t].w, v = ve[i][t].v;
				if(j >= w) f[j] = max(f[j], f[j - w] + v);
			}
	printf("%d\n", sum - f[k]);
	
	return 0;
}

A. Zebras(思维 + set)

比赛时有两种思路,一种是推出规律,找1的个数和0的个数的关系,结果没推出来

第二种就是for很多遍,每一遍都得出一个序列

其实2e5的数据,尽管会删除数据,for很多遍肯定超时的,我当时就是太着急了,有一个思路还没想正确性就开始写,浪费时间又WA

那么正解肯定是for一遍,O(n)或者O(nlogn)

我想了挺久,发现可以边for就边加入各个序列当中,一个1就要找到末尾为0的序列,一个0就要找到末尾为1的序列

一开始遍历一遍末尾符合的序列结果超时

后来反应到可以用两个set来维护末尾为1的序列和末尾为0的序列的集合,每次都是logn

然后就AC了

还是挺有趣的,这个思维我还是过了很久才想到,还是做题太少,继续加油

#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 = 2e5 + 10;
vector<int> ans[N];
set<int> s1, s2; //s1末尾为1 s2末尾为0 
int cnt;

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	
	string s;
	cin >> s;
	int len = s.size();
	
	int flag = 1;
	REP(k, 0, len)
	{
		if(s[k] == '0')
		{
			if(s1.empty())
			{
				ans[++cnt].push_back(k + 1);
				s2.insert(cnt);
			}
			else
			{
				int t = *s1.begin();  //begin()是迭代器,看作指针 
				ans[t].push_back(k + 1);
				s1.erase(t); s2.insert(t);
			}
		}
		else
		{
			if(s2.empty()) { flag = 0; break; }
			else
			{
				int t = *s2.begin(); 
				ans[t].push_back(k + 1);
				s2.erase(t); s1.insert(t);
			}
		}
	}
	_for(i, 1, cnt)
		if(ans[i].size() % 2 == 0)
		{
			flag = 0;
			break;
		}
	
	if(!flag) puts("-1");
	else
	{
		printf("%d\n", cnt);
		_for(i, 1, cnt)
		{
			printf("%d ", ans[i].size());
			for(auto x: ans[i])
				printf("%d ", x);
			puts("");
		}
	}
	
	return 0;
}

 

今天一口气补了四道题,都是独立做出,很爽

其实还是有实力做出来cf 1500到2000的题目的,但是考试时发挥不出来。

考试时应该静下心想题,想清楚,像平时一样,这是我要改进的

 

周二 3.23 (杂题 + 欧拉回路)

AtCoder - agc006_b(构造题)

说实话这道题我是一点思路都没有

感觉挺复杂,因为每三个数都要取一遍

最后看了题解

发现很巧妙,只要中间放上x - 1 x x + 1就可以了

可以推出来这样一直往上,x会一直在中间

没想到这么简单

这种构造题真的挺巧妙的,看似很复杂,却能用一种很简单的方式解决

构造题不要想太复杂,往往解法很简单。不要有心理障碍

#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 = 2e5 + 10;
int ans[N];

int main()
{
	int n, x;
	scanf("%d%d", &n, &x);
	if(x == 1 || x == 2 * n - 1) 
	{
		puts("No");
		return 0;
	}
	
	ans[n - 1] = x - 1;
	ans[n] = x;
	ans[n + 1] = x + 1;
	
	int now = 0;
	_for(i, 1, 2 * n - 1)
	{
		if(ans[i]) continue;
		now++;
		if(now == x - 1) now = x + 2;
		ans[i] = now;
	}
	
	puts("Yes");
	_for(i, 1, 2 * n - 1) printf("%d\n", ans[i]);
	
	return 0;
}

P2731 [USACO3.3]骑马修栅栏 Riding the Fences(欧拉回路模板题)

欧拉回路可以很形象地理解为一笔画问题

一笔画就是一条路径,每条边经过且仅经过一次

这样的路叫做欧拉路

如果是一条回路,就交欧拉回路

理解了这个定义,那么一些性质就很容易理解

无向图中,欧拉回路度数都为偶数,欧拉路仅有两个度数为奇数的点

有向图中,欧拉回路入度=出度,欧拉路中一个点入度多1,一个点出度多1,其他入度等于出度

 

那么这么求呢?

算法的思路是这样的,找到一个环,删除,继续找一个环,然后把两个环拼接成一个环,一直重复下去

看起来复杂,思路很简单,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 = 500 + 10;
int g[N][N], d[N], m, mx, mi = 1e9;
stack<int> ans;

void dfs(int u)
{
	_for(v, mi, mx)
		if(g[u][v])
		{
			g[u][v]--;
			g[v][u]--;
			dfs(v);
		}
	ans.push(u);
}

int main()
{
	scanf("%d", &m);
	while(m--)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		g[u][v]++; g[v][u]++;
		d[u]++; d[v]++;
		mi = min(mi, min(u, v));
		mx = max(mx, max(u, v));
	}
	
	int st = mi;
	_for(i, mi, mx)
		if(d[i] & 1)
		{
			st = i;
			break;
		}
	
	dfs(st);
	while(!ans.empty())
	{
		printf("%d\n", ans.top());
		ans.pop();
	}

	return 0;
}

周三 3.24 (欧拉回路)

先再明确一下定义

欧拉路是每条边只经过一次且经过所有顶点,即一笔画经过所有点

是否存在欧拉路或欧拉回路的判定:

有两个条件

一个是 图是联通的,有向边则视为无向边(称为基图),可以用并查集维护,最后为一个联通分量

一个是度数的关系,这个是很容易推出来的

如果要求具体的路径才dfs

「一本通 3.7 例 2」单词游戏(建模 + 欧拉路判定)

这道题搞了我好久

建模真的太骚了

第一反应可以是单词为点,然后去连边,找一条路径使得每个点只经过一次

第一连边会超时,第二这种路径是哈密尔顿路,求这个路很麻烦

然后我就灵光一闪,把26个字母看作点,单词看作边

这样每条边只经过一次,这样建模不就刚好是欧拉路的定义吗

然后我又卡住了,单词看作边要怎么连呢

我开始是想末字母有一条出边,首字母有一条入边,然后看度数的关系

可是判顶的时候还要看联通,所以不能光看度数的关系

最后我看了题解,竟然是一个单词首字母向末字母连一条边

太秀了

这样子没那么直接,但是分析一波是对的,这样保留了这个单词的信息

因此根据前面说的那两个条件判定欧拉图就好了

#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;
int in[30], out[30], f[30], vis[30], n, cnt;
char s[N];

int find(int x)
{
	if(f[x] == x) return x;
	return f[x] = find(f[x]);
}

int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		memset(in, 0, sizeof(in));
		memset(out, 0, sizeof(out));
		memset(vis, 0, sizeof(vis));
		_for(i, 0, 25) f[i] = i;
		cnt = 0;
		
		scanf("%d", &n);
		_for(i, 1, n)
		{
			scanf("%s", s);
			int len = strlen(s);
			int u = s[0] - 'a', v = s[len - 1] - 'a';
			out[u]++; in[v]++; 
			if(!vis[u]) vis[u] = 1, cnt++;
			if(!vis[v]) vis[v] = 1, cnt++;
			if(find(u) != find(v)) cnt--;
			f[find(u)] = find(v);
		}
	
		int cnt1 = 0, cnt2 = 0, flag = 1;
		_for(i, 0, 25)
		{
			if(in[i] == out[i]) continue;
			if(in[i] - out[i] == 1) cnt1++;
			else if(out[i] - in[i] == 1) cnt2++;
			else { flag = 0; break; }
		}
		
		if(cnt != 1 || !flag || !( (!cnt1 && !cnt2) || (cnt1 == 1 && cnt2 ==1))) puts("The door cannot be opened.");
		else puts("Ordering is possible.");
	}
	
	return 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 = 1e3 + 10;
int in[30], out[30], n, sum;
struct node{ int v, vis; };
vector<node> g[N];
char s[N];

void dfs(int u)
{
	for(auto& x: g[u]) //如果要修改边的值的话,这里要引用 
		if(!x.vis)
		{
			x.vis = 1;
			dfs(x.v);
			sum++;
		}
}

int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		memset(in, 0, sizeof(in));
		memset(out, 0, sizeof(out));
		_for(i, 0, 25) g[i].clear();
		
		scanf("%d", &n);
		_for(i, 1, n)
		{
			scanf("%s", s);
			int len = strlen(s);
			int u = s[0] - 'a', v = s[len - 1] - 'a';
			g[u].push_back(node{v, 0});
			out[u]++; in[v]++; 
		}
	
		int cnt1 = 0, cnt2 = 0, flag = 1, st = -1;
		_for(i, 0, 25)
		{
			if(in[i] == out[i]) continue;
			if(in[i] - out[i] == 1) cnt1++;
			else if(out[i] - in[i] == 1) cnt2++, st = i;
			else { flag = 0; break; }
		}
		
		if(!flag || !( (!cnt1 && !cnt2) || (cnt1 == 1 && cnt2 ==1))) 
		{
			puts("The door cannot be opened.");
			continue;
		}
		
		if(st == -1)
			_for(i, 0, 25)
				if(out[i])
				{
					st = i;
					break;
				}
		sum = 0;
		dfs(st);
		if(sum == n) puts("Ordering is possible.");
		else puts("The door cannot be opened.");
	}
	
	return 0;
}

hdu 1878 (判定欧拉图模板题)

#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;
int deg[N], f[N], n, m; 

int find(int x)
{
	if(f[x] == x) return x;
	return f[x] = find(f[x]);
}

int main()
{
	while(scanf("%d", &n) && n)
	{
		memset(deg, 0, sizeof(deg));
		_for(i, 1, n) f[i] = i;
		int cnt = n;
		
		scanf("%d", &m);
		while(m--)
		{
			int u, v;
			scanf("%d%d", &u, &v);
			deg[u]++; deg[v]++;
			if(find(u) != find(v)) cnt--;
			f[find(u)] = find(v);
		}
		
		int flag = 1;
		_for(i, 1, n)
			if(deg[i] & 1)
			{
				flag = 0;
				break;
			}
		if(flag && cnt == 1) puts("1");
		else puts("0");
	}
	
	return 0;
}

周四 3.25 (欧拉回路)

「一本通 3.7 练习 2」Ant Trip(欧拉回路 + 找规律猜结论)

一开始想暴力,就是不断跑欧拉路不断删边,看能跑多少次

然后超时了

后来想到可以通过度数来判定,不用真的去跑欧拉回路,要具体路径才跑

那么有欧拉路或者欧拉回路前提是联通,所以用并查集维护多个联通分量

然后我就任意画图看要几笔才能画完,感觉和奇点个数有关

然后我一直画不出奇点个数为奇数的图,一直是0 2 4 6

我还发现,0 和 2的时候是一次(也即欧拉路判定条件),而4个奇点要2笔,6个奇点要3笔

发现每画一笔会少两个奇点。那么就可以按照这个算个数了

交上去WA了一发,发现忽略了孤立点的情况。

孤立点要删掉,因为其根本没有边,欧拉路是经过每个边有且仅有一次。

因此统计答案时忽略孤立点就好了

#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 deg[N], f[N], cnt[N], n, m; 

int find(int x)
{
	if(f[x] == x) return x;
	return f[x] = find(f[x]);
}

int main()
{
	while(~scanf("%d%d", &n, &m))
	{
		memset(deg, 0, sizeof(deg));
		memset(cnt, 0, sizeof(cnt));
		_for(i, 1, n) f[i] = i;
		
		while(m--)
		{
			int u, v;
			scanf("%d%d", &u, &v);
			deg[u]++; deg[v]++;
			f[find(u)] = find(v);
		}
		
		_for(i, 1, n)
			if(deg[i] & 1)
				cnt[find(i)]++;
		
		int ans = 0;
		_for(i, 1, n)
			if(deg[i] && find(i) == i)
			{
				if(cnt[i] <= 2) ans++;
				else ans += cnt[i] / 2;
			}
		printf("%d\n", ans);
	}
	
	return 0;
}

「一本通 3.7 练习 3」John's Trip(欧拉回路模板题)

输出边的顺序就好了

注意两个点

1.dfs之前要先判断一下度数,度数正确是前提。如果度数不对,即使dfs可以得到一个路径,也是不行的

2.如果题目要求输出边的编号,那么删边的时候用一个vis数组来保存是否访问过就好。

3.注意重边和自环。删边用边的编号删去,不要用map<pair<int, int>, bool> 这个无法处理重边的情况

#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 = 50;
const int M = 2000;
struct node{ int v, id; };
vector<node> g[N];
stack<int> ans;
int deg[N], vis[M], n, m, st, cnt;

void dfs(int u)
{
	for(node x: g[u])
	{
		int v = x.v, id = x.id;
		if(!vis[id])
		{
			vis[id] = 1;
			dfs(v);
			ans.push(id);
		}
	}
}

int main()
{
	int x, y, z;
	while(scanf("%d%d", &x, &y) && x && y)
	{
		cnt = 1;
		REP(i, 0, N) g[i].clear();
		memset(deg, 0, sizeof(deg));
		memset(vis, 0, sizeof(vis));
		st = min(x, y);
		
		scanf("%d", &z);
		g[x].push_back(node{y, z});
		g[y].push_back(node{x, z});
		deg[x]++; deg[y]++;
		
		while(scanf("%d%d", &x, &y) && x && y)
		{
			scanf("%d", &z);
			g[x].push_back(node{y, z});
			g[y].push_back(node{x, z});	
			deg[x]++; deg[y]++;
			cnt++;
		}
		
		int flag = 1;
		REP(i, 0, N) //一定先判度数 
			if(deg[i] & 1)
			{
				puts("Round trip does not exist.");
				flag = 0;
				break;
			}
		if(!flag) continue;
		
		while(!ans.empty()) ans.pop();
		dfs(st);
		if(ans.size() != cnt) puts("Round trip does not exist.");
		else
		{
			while(!ans.empty())
			{
				printf("%d", ans.top());
				if(ans.size() == 1) puts("");
				else printf(" ");
				ans.pop();
			}
		}
	}
	
	return 0;
}

周五 3.26 (欧拉回路)

「一本通 3.7 练习 4」太鼓达人(欧拉回路 + 建模)

我又一次跳坑,二进制数看作点,去连边

花了一个小时写完后发现错了。

突然意识这道题和单词接龙的非常像,可以说是一道题

同样,一个二进制数就是一条边,头向尾连,这时头是除去最后一个字符,尾是出去第一个字符

欧拉回路建模的核心在于明确什么是边,边经过且只经过一次

然后还有一些细节看代码

#include<bits/stdc++.h>
#define mp make_pair
#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 = 1100;
map<pair<int, int>, bool> vis;
vector<int> g[N];
stack<int> ans;
int k;

int num(string s)
{
	int res = 0, len = s.size();
	REP(i, 0, len)
	{
		res <<= 1; //乘2写上面,不然最后多乘一次 
		res += s[i] - '0';
	}
	return res;
}

void dfs(int u)
{
	for(int v: g[u])
		if(!vis[mp(u, v)]) //没有重边可以用map删边. 
		{
			vis[mp(u, v)] = 1;
			dfs(v);
		}
	ans.push(u); //点在循环结束后加入 
}

int main()
{
	cin >> k;
	REP(i, 0, 1 << k)
	{
		string t = "", t1 = "", t2 = ""; //初始化,类似int x = 0 
		REP(j, 0, k)
		{
			if(i & (1 << j)) t += "1";
			else t += "0";
		}
		REP(j, 0, k - 1) t1 += t[j], t2 += t[j + 1]; //注意string用法 是 += 不是 = 
		g[num(t1)].push_back(num(t2)); //注意是有向边 
	}
	
	REP(i, 0, 1 << (k - 1)) sort(g[i].begin(), g[i].end()); //排序保证字典序 
	dfs(0);
	cout << (1 << k) << " ";
	
	while(ans.size() > 1) //最后回到起点不用输出 
	{
		int t = ans.top();
		if(t & (1 << (k - 2))) cout << "1";
		else cout << "0";
		ans.pop();
	}

	return 0;
}

周六 3.27 (欧拉回路)

「一本通 3.7 练习 6」原始生物(欧拉回路 + 建模 + 找规律)

这道题感觉就像是前面几道题的综合

首先建模很套路了,一个遗传密码就是一条边,首向尾连接

然后问题就变成了一个有向图,需要几笔画才能画完

我开始跑欧拉回路,看能跑几次。发现在不存在一笔画完的情况下跑欧拉回路是错误的

这个图存在欧拉回路再去跑才是对的

那么显然就要看每个联通分量的度数了

这里我卡了很久,没什么思路

然后我意识到,一条路径,起点出度-1,终点入度-1,中间的点的出度和入度减去相同的数

也就是除非是作为起点和终点,否则一个点的出度和入度的差值是相同的

那么比如一个点出度比入度大3,那么这个点一定要以它为起点跑3次

所以我们可以统计所有出度大于入度时的差值和(入度大于出度叶可以,一样的),就是要跑的次数

这里有个坑点就是,差值和为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;
int in[N], out[N], f[N], cnt[N], cnt1[N], n;

int find(int x)
{
	if(f[x] == x) return x;
	return f[x] = find(f[x]);	
} 

int main()
{
	_for(i, 1, 1000) f[i] = i;
	scanf("%d", &n);
	while(n--)
	{
		int l, r;
		scanf("%d%d", &l, &r);
		out[l]++; in[r]++;
		
		int x = find(l), y = find(r);
		if(x == y) cnt[x]++;
		else
		{
			f[x] = y;
			cnt[y] += cnt[x] + 1;
		}
	}
	
	_for(i, 1, 1000)
		if(out[i] > in[i]) 
			cnt1[find(i)] += out[i] - in[i];
	
	int ans = 0;
	_for(i, 1, 1000)
		if((in[i] || out[i]) && f[i] == i)
			ans += cnt[i] + max(cnt1[i], 1);
	printf("%d\n", ans);

	return 0;
}

周日 3.28 (欧拉回路)

「一本通 3.7 练习 5」相框(欧拉回路)

这题太骚了,我独立做到80分,有一种情况没考虑到

首先抽象出模型,以度数为核心考虑

烧熔操作可以理解为把度数拆开,例如3拆成1 + 2

那么为了最终成一个环,要分两种情况

如果只有一个联通分量就简单,大于2的全部拆成一堆2,如果为奇数就多留下一个1

为什么要2呢,因为环上每个度数都为2

从度数满足去推为一个环,我手画了一下,发现度数对了一定有其中一种方式可以弄成环。不一定度数对了就是环,但一定有一种方式度数对了就是环

之后合并,把奇数对合并就好

如果有多个联通分量就麻烦

目标是每个联通分量弄成一条链,最后一起合并成一个大环

对于其中的一个联通分量

同样大于2的点拆,最后合并度数为1的点

这里有个特例,就是不存在奇数点的情况

我开始想的是最后再把环拆成链就行了

然后我没想到的是,当烧熔的过程中,如果度数大于2,比如4,是可以拆成2 + 1 + 1的

也就是说,如果有点度数大于2,就不用最后再把环拆为链

就是这个点让我一直80分

此外,如果有一端为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 = 1e5 + 1e3 + 10;
int deg[N], f[N], num[N], k[N], n, m;

int find(int x)
{
	if(f[x] == x) return x;
	return f[x] = find(f[x]);
}

int main()
{
	REP(i, 1, N) f[i] = i;
	scanf("%d%d", &n, &m);
	while(m--)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		if(!u) deg[u = ++n]++; else deg[u]++;
		if(!v) deg[v = ++n]++; else deg[v]++;
		f[find(u)] = find(v);
	}
	
	int cnt = 0;
	_for(i, 1, n)
		if(deg[i] && f[i] == i) //联通分量个数 
			cnt++;
	
	int ans = 0;
	if(cnt == 1)
	{
		int t = 0;
		_for(i, 1, n)
		{
			if(deg[i] > 2) ans++;
			if(deg[i] & 1) t++; //奇数个数 
		}
		ans += t / 2;       //合并两端 
	}
	else
	{
		_for(i, 1, n)
		{
			if(deg[i] > 2) ans++, k[find(i)] = 1; 
			if(deg[i] & 1) num[find(i)]++; //奇数个数 
		}
		_for(i, 1, n)
			if(deg[i] && f[i] == i)
			{
				if(num[i] == 0) ans += !k[i]; 
				else ans += num[i] / 2 - 1; 
			}
		ans += cnt;
	}
	printf("%d\n", ans);
		
	return 0;
}

现在开始如果队内没有比赛的话,自己弄一套div1 abc,英文要适应

 

A. Basic Diplomacy(构造)

看似复杂的这种构造题

YES与NO的区分条件往往是很简单,很极端的

这道题只要必须选的人超过m/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 = 1e5 + 10;
vector<int> g[N];
int cnt[N], n, m;
	
int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		memset(cnt, 0, sizeof(cnt));
		scanf("%d%d", &n, &m);
		
		int mx = 0;
		_for(i, 1, m) 
		{
			g[i].clear();
			int k; scanf("%d", &k);
			_for(j, 1, k)
			{
				int x; scanf("%d", &x);
				g[i].push_back(x);
				if(k == 1) mx = max(mx, ++cnt[x]);
			}
		}
		
		if(mx > (m + 1) / 2) puts("NO");
		else
		{
			puts("YES");
			memset(cnt, 0, sizeof(cnt));
			_for(i, 1, m)
				if(g[i].size() == 1)
					cnt[g[i][0]]++;
			_for(i, 1, m)
			{
				if(g[i].size() == 1) printf("%d ", g[i][0]);
				else 
				{
					int ma = 1e9, t;
					for(int j: g[i]) 
						if(ma > cnt[j])
						{
							ma = cnt[j];
							t = j;
						}
					printf("%d ", t);
					cnt[t]++;
				}
		
			}
			puts("");
		}
	}
		
	return 0;
}

晚上做b做着有点疲惫了

明天把bc做了,每周一套div1 abc

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值