大一下第七周学习笔记

博主本周专注于割点和桥相关算法题训练,包括边双联通分量缩点、点双联通分量等题目,还做了CF杂题和训练赛补题。如在求点双联通分量时有不同方法,做思维题需大胆猜结论等,下周计划补题并学习缺失知识点。

继续早睡早起锻炼身体

规划好时间

这周就割点和桥,一场个人比赛

周一 4.12(割点和桥)

「一本通 3.6 例 1」分离的路径 (边双联通分量缩点)

换了一种写法,很类似Tarjan的缩点的写法

这样就不用真的去写删边,搜索的过程中就顺便求出来了

这样写代码会简洁一些

注意最后还剩下根节点那个联通分量,要注意

#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 = 5e3 + 10;
int node[N], deg[N], st[N], num, top;
int dfn[N], low[N], cnt, n, m;
vector<int> g[N];
 
void dfs(int u, int fa)
{
	dfn[u] = low[u] = ++cnt;
	st[++top] = u;
	bool vis = false;
	for(auto v: g[u])
	{
		if(!dfn[v])
		{
			dfs(v, u);
			low[u] = min(low[u], low[v]);
			if(low[v] > dfn[u]) 
			{
				num++;
				while(1)
				{
					node[st[top--]] = num;
					if(st[top + 1]  == v) break;
				}
			}
		}
		else
		{
			if(v == fa && !vis) vis = true;
			else low[u] = min(low[u], dfn[v]);
		}
	}
}
 
int main()
{
	scanf("%d%d", &n, &m);
	_for(i, 1, m)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		g[u].push_back(v);
		g[v].push_back(u);
	}
	
	dfs(1, 0);
	num++;
	while(top) node[st[top--]] = num;
	
	_for(u, 1, n)
		for(auto v: g[u])
			if(node[u] > node[v])
				deg[node[u]]++, deg[node[v]]++;
	
	int sum = 0;
	_for(i, 1, num)
		sum += deg[i] == 1;
	printf("%d\n", (sum + 1) / 2);
		
	return 0;
}

周二 4.13(割点和桥)

昨天今天训练时间是够的,只是一直卡在一道题上,所以从题量上看上去比较少

[HNOI2012]矿场搭建(点双联通分量)

这题搞了我好久

首先要明确一个概念

点双联通分量也叫块。块和边双联通分量不一样,边双联通分量是去掉割边之后就是边双联通分量

而块中,一个割点一定属于两个以上的块,两个块的公共点是割点

怎么求呢

可以边tarjan边求,边搜边把点入栈,然后遇到割点就出栈一直到割点的前一个点

这个时候割点要记入这个块中,但不要出栈,因为割点属于2个以上的块,后面的块还会包括这个割点

这里还有一个问题,就是根节点如果不是割点的话,它是不会入栈的,最后会剩下来

这和求边双联通分量的时候还剩下一个一样。

所以要特判一下。

但其实这种写法挺麻烦的,还有一种更简单的方法,就是分开求,先求出割点

然后dfs,每次找一个非割点的点来dfs,遍历这个点所在的块,这时注意不能搜到其他块里面,而两个块的公共点就是割点,所以

遇到割点的时候就不要继续搜下去了。

 

回到这道题

这道题说点没了,那显然和割点有关

我们考虑每一个块

如果没有割点,那就随意设立两个点,方案是C(n,2)

如果有一个割点,那么如果这个割点没了,这个块中的其他点就和其他部分分开了,所以这个块的非割点要设立一个,方案数为块的节点数-1,也就是去掉割点

如果有两个割点,去掉一个割点,其他点可以通过另一个割点跑到其他的块中,而对于另一个块,如果这个公共割点是它唯一的割点,它会设立一个,如果不是,那么可以继续从其他块的另一个割点跑到第3个块

第3个块同样,这样迭代下去是可以获救的。因此如果有两个割点就不用设立

 

然后这题实现的时候也有很多细节要注意。我们需要算出每个块的割点数以及节点数

每一个块中,每一个割点只能访问一次,访问多了节点数割点数就不对了。

为了避免每次都memset数组,我就用一个unordered_map来记录

还有防止重复遍历节点的vis数组不能标记割点,因为其他块还会搜到割点

细节要注意

我写程序时一些变量名要表示它的作用

#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;
int dfn[N], low[N], cnt, n, m, root;
int st[N], cut[N], vis[N], num, top, node, sum;
unordered_map<int, bool> vis_cut;
vector<int> g[N];
 
void tarjan(int u) //求割点 
{
	dfn[u] = low[u] = ++cnt;
	st[++top] = u;
	int son = 0;
	for(int v: g[u])
	{
		if(!dfn[v])
		{
			tarjan(v);
			low[u] = min(low[u], low[v]);
			if((u == root && ++son > 1) || (u != root && low[v] >= dfn[u])) cut[u] = 1;
		}
		else low[u] = min(low[u], dfn[v]); //已经出栈的点不要算 
	}    //无向图没有横叉边,不写也没关系。
}

void dfs(int u)
{
	vis[u] = 1; node++;
	for(int v: g[u])
	{
		if(vis[v]) continue;
		if(cut[v])
		{
			if(!vis_cut[v]) 
				vis_cut[v] = 1, node++, sum++;
		}
		else dfs(v);
	}
}
 
int main()
{
	int kase = 0;
	while(scanf("%d", &m) && m)
	{
		num = top = cnt = n = 0;
		memset(dfn, 0, sizeof(dfn));
		memset(low, 0, sizeof(low));
		memset(cut, 0, sizeof(cut));
		memset(vis, 0, sizeof(vis));
		_for(i, 1, N - 1) g[i].clear();
		
		while(m--)
		{
			int u, v;
			scanf("%d%d", &u, &v);
			g[u].push_back(v);
			g[v].push_back(u);
			n = max(n, max(u, v));
		}
		
		_for(i, 1, n)
			if(!dfn[i]) //可能不连通,dfs时要小心 
				tarjan(root = i);
				
		long long res = 1, t = 0;
		_for(i, 1, n)
			if(!vis[i] && !cut[i])
			{
				node = sum = 0;
				vis_cut.clear();
				
				dfs(i);
				if(sum == 0) t += 2, res *= node * (node - 1) / 2;
				if(sum == 1) t++, res *= (node - 1);
			}
		printf("Case %d: %lld %lld\n", ++kase, t, res);
	}
		
	return 0;
}

「一本通 3.6 练习 1」网络(割点模板题)

注意两个点

(1)割点判断条件low[v] >= dfn[u]不要写错

(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 = 110;
int dfn[N], low[N], cut[N], cnt,  n, root;
vector<int> g[N];

void tarjan(int u)
{
	dfn[u] = low[u] = ++cnt;
	int son = 0;
	for(int v: g[u])
	{
		if(!dfn[v])
		{
			tarjan(v);
			low[u] = min(low[u], low[v]);
			if(u == root && ++son > 1 || u != root && low[v] >= dfn[u]) cut[u] = 1; //注意一个割点会被判断多次 
		}
		else low[u] = min(low[u], dfn[v]);
	}
}
 
int main()
{
	while(scanf("%d", &n) && n)
	{
		_for(i, 1, n) g[i].clear();
		memset(dfn, 0, sizeof(dfn));
		memset(low, 0, sizeof(low));
		memset(cut, 0, sizeof(cut));
		cnt = 0;
		
		int u, v;
		while(scanf("%d", &u) && u)
			while(1)
			{
				scanf("%d", &v);
				g[u].push_back(v);
				g[v].push_back(u);
				if(getchar() == '\n') break;
			}
		
		_for(i, 1, n)
			if(!dfn[i])
				tarjan(root = i);
		
		int ans = 0;
		_for(i, 1, n)
			ans += cut[i];
		printf("%d\n", ans);
	}
		
	return 0;
}

周三 4.14(割点和桥)

今天的训练时间就是一个下午,一个下午都再写一道题

P5058 [ZJOI2004]嗅探器(求割点拓展)

按照题目的意思,两个点的任何路径都要经过所求的点

那么我马上就想到这个点是割点,然后两个块之间的公共点是这个割点,a,b属于不同的块

然后就这么写了,结果80分

一直不知道哪里错了,后来发觉可以a,b所在的块可以不相邻,可以中间经过几个割点

这么一搞就很难弄了,我尝试了把一个块缩成一个点,要给割点缩成一条边的方式,发现实现起来很麻烦,因为会有连续几个割点的情况。

后来看了题解,发现题解非常巧妙,是在tarjan的过程中顺便就求出了

而没有往块这个方向想

理一下思路,首先所求点为一个割点,但这个割点去掉以后一定要使得a,b不联通

所以我们从a开始tarjan,然后搜到一个割点的时候(除了根节点)

我们考虑这个割点会不会导致a,b不联通

u的儿子是v,u是割点,如果去掉u,就会使得v这颗子树和u父亲的那一个部分分离。

因此如果要使得a,b不联通,那么b一定在v或v的子树当中

如果b 为v 则dfn[b] = dfn[v]

如果b在v的子树中,则dfn[b] > dfn[v]

注意我们判断割点的时候,一定是搜到v的子树中回溯回来的。

所以这个时候判断就一定使得满足dfn[b] > dfn[v],b一定在v的子树当中

所以综合一下就是dfn[b] >= dfn[v]

#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 dfn[N], low[N], cut[N];
int cnt, root, n, a, b;
vector<int> g[N];

void tarjan(int u)
{
	dfn[u] = low[u] = ++cnt;
	for(int v: g[u])
	{
		if(!dfn[v])
		{
			tarjan(v);
			low[u] = min(low[u], low[v]);
			if(u != root && low[v] >= dfn[u] && dfn[b] >= dfn[v]) cut[u] = 1;
		}
		else low[u] = min(low[u], dfn[v]);
	}
}

int main()
{
	scanf("%d", &n);
	int u, v;
	while(scanf("%d%d", &u, &v) && u && v)
	{
		g[u].push_back(v);
		g[v].push_back(u);
	}
	scanf("%d%d", &a, &b);
	
	tarjan(root = a);
	_for(i, 1, n)
		if(cut[i])
		{
			printf("%d\n", i);
			return 0;
		}
	puts("No solution");
	
	return 0;
}

花10分钟秒了一道割边模板题

「一本通 3.6 练习 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;

const int N = 3e4 + 10;
int dfn[N], low[N];
int cnt, n, m, ans;
vector<int> g[N];

void tarjan(int u, int fa)
{
	dfn[u] = low[u] = ++cnt;
	bool vis = false; //重边 
	for(int v: g[u])
	{
		if(!dfn[v])
		{
			tarjan(v, u);
			low[u] = min(low[u], low[v]);
			if(low[v] > dfn[u]) ans++;
		}
		else 
		{
			if(v == fa && !vis) vis = true;
			else low[u] = min(low[u], dfn[v]);
		}
	}
}

int main()
{
	while(scanf("%d%d", &n, &m) && n && m)
	{
		cnt = ans = 0;
		_for(i, 1, n) dfn[i] = low[i] = 0, g[i].clear();
		
		while(m--)
		{
			int u, v;
			scanf("%d%d", &u, &v);
			g[u].push_back(v);
			g[v].push_back(u);
		}
		
		tarjan(1, 0);
		printf("%d\n", ans);
	}
	
	return 0;
}

周四 4.15(割点和桥)

「一本通 3.6 练习 4」电力(割点能分割的联通分量数)

有了昨天那一题的启发,这题就很容易了,在tarjan的过程中统计就好

这个割点能确定一次,就说明能分离一个联通分量,如果它被确定了第二次,说明又可以多一个联通分量

所以我们就先统计原来的联通分量个数

然后统计每个割点去掉能多几个联通分量,取最大值就好了

注意这道题有个坑点就是边数可以为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;
int dfn[N], low[N], ans[N], root, cnt, n, m;
vector<int> g[N];

void tarjan(int u)
{
	dfn[u] = low[u] = ++cnt;
	int son = 0;
	for(int v: g[u])
	{
		if(!dfn[v])
		{
			tarjan(v);
			low[u] = min(low[u], low[v]);
			if(u == root && ++son > 1 || u != root && low[v] >= dfn[u]) ans[u]++;
		}
		else low[u] = min(low[u], dfn[v]);
	}
}

int main()
{
	while(scanf("%d%d", &n, &m))
	{
		if(n == 0 && m == 0) break;
		cnt = 0;
		memset(dfn, 0, sizeof(dfn));
		memset(low, 0, sizeof(low));
		memset(ans, 0, sizeof(ans));
		_for(i, 1, n) g[i].clear();
		
		if(m == 0)
		{
			printf("%d\n", n - 1);
			continue;
		}
		
		while(m--)
		{
			int u, v;
			scanf("%d%d", &u, &v);
			u++; v++;
			g[u].push_back(v);
			g[v].push_back(u);
		}
		
		int sum = 0;
		_for(i, 1, n)
			if(!dfn[i])
			{
				sum++;
				tarjan(root = i);
			}
		
		int mx = 0;
		_for(i, 1, n)
			mx = max(mx, ans[i]);
		printf("%d\n", mx + sum);
	}
	
	return 0;
}

「一本通 3.6 练习 5」Blockade(tarjan求割点拓展)

和上一题大同小异吧,再拓展一下

首先一个割点去掉,一定会有2 * (n - 1)个点对没了,因为这个点本身没了

然后看分割成几个联通分量,设每个联通分量点的个数为num

没有的点对数为A(n - 1, 2) - A(num1, 2) - A(num2, 2)……

为了求联通分量点的个数,我们就可以dfs一遍求num[u]表示以u为根的子树的节点个数

最后注意根节点求联通分量个数要特判一下

这样一本通的这个专题就刷完了,我的效率还是很ok的

接下来做一波cf杂题

#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 = 1e5 + 10;
int dfn[N], low[N], sum[N], root, cnt, n, m;
vector<int> g[N], ans[N];
ll res[N];

void tarjan(int u)
{
	dfn[u] = low[u] = ++cnt;
	for(int v: g[u])
	{
		if(!dfn[v])
		{
			tarjan(v);
			low[u] = min(low[u], low[v]);
			if(u == root) ans[u].push_back(sum[v]);
			if(u != root && low[v] >= dfn[u]) ans[u].push_back(sum[v]);
		}
		else low[u] = min(low[u], dfn[v]);
	}
}

void dfs(int u)
{
	sum[u] = 1;
	for(int v: g[u])
		if(!sum[v])
		{
			dfs(v);
			sum[u] += sum[v];
		}	
}

ll cal(int x) { return 1ll * x * (x - 1); }

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);
	}
	
	dfs(1);
	tarjan(root = 1);
	
	if(ans[root].size() > 1)
	{
		res[1] = cal(n - 1);
		for(int num: ans[root])
			res[1] -= cal(num);
	}
	
	_for(i, 2, n)
		if(ans[i].size())
		{
			res[i] = cal(n - 1); ll t = 0;
			for(int num: ans[i])
			{
				res[i] -= cal(num);
				t += num;
			}
			res[i] -= cal(n - 1 - t);
		}
		
	_for(i, 1, n)
		printf("%lld\n", res[i] + (n - 1) * 2);
				
	return 0;
}

周五 4.16 (cf1900杂题)

D. Fill The Bag(思维)

这道题没做出来,是看题解的

我想的时候纠结相加起来1的位置改变的问题,不知道怎么搞

其实这正是关键,这提醒我们可以枚举1的位置,从小到大

接下来就贪心了

我们分bag的1和box的1

对于当前位

如果bag不需要放,那么我们就把box当前位的1相加,给到下一位

如果需要放,box有就放,然后给下一位

没有那就找一个最近的1,然后下一次循环就从这个最近的1开始就行了

其实想清楚了就蛮简单的,但是当时没想到。还是多做题吧

这是一种思路,写一个循环,当前的信息一直传递给后面

#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;
int bag[70], box[70], m;
ll n;
 
int f(int x) 
{
	int res = 0;
	for(; x; x >>= 1) res++;
	return res;
}
 
int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		memset(bag, 0, sizeof(bag));
		memset(box, 0, sizeof(box));
		scanf("%lld%d", &n, &m);
		
		int pos = 0;
		for(ll t = n; t; t >>= 1)
			bag[++pos] = t & 1;
		
		ll sum = 0;
		while(m--)
		{
			int x; scanf("%d", &x);
			sum += x;
			box[f(x)]++;
		}
		
		if(sum <= n)
		{
			puts(sum < n ? "-1" : "0");
			continue;
		}
		
		int ans = 0;
		_for(i, 1, pos)
		{
			if(bag[i] == 0) box[i + 1] += box[i] / 2;
			else
			{
				if(box[i]) box[i]--, box[i + 1] += box[i] / 2;
				else
				{
					int t = i + 1;
					while(!box[t]) t++;
					ans += t - i;
					box[t]--;
					i = t - 1;
				}
			}
		}
		printf("%d\n", ans);
	}
	
	return 0;
}

周日 4.18(训练赛补题)

昨天队内打了一场队内训练赛,还行吧。认真补题,补每一道题

1.vjudge网站问题下次要解决,可以带个笔记本电脑过来

2.不要死磕一道题。比赛时我死磕一道题,以为自己思路很正确,就是差一点优化。但是后来看了题解发现自己思路是错误的。

所以不要在一道题上花太多时间

P1972 [SDOI2009]HH的项链(离线 + 树状数组)

由于前段时间一直在做线段树,区间染色问题等等

这道题一读完题马上开打线段树,然后超时了

我还纠结为什么超时,不用线段树用什么

现在看来,用线段树是会超时的,这个思路是错的

现在来看看正解

这题和之前的线段树题有一点不同,就是没有修改,是静态的

也就是说可以离线

首先一段区间里面不同颜色的个数,就是区间长度减去重复的个数

那么问题的关键在于如果算出重复的个数

一段区间1 2 3 1

这里重复了一个1,显然要删掉一个1

那么按照正常的逻辑从小到大,我们删掉左边那个1

所以重复的元素就用最右边那个元素代表

那么一些区间,如果它们右端点一样的话,那么每个元素最右端的位置是一样的

所以我们按照区间右端点排序

用树状数组来维护这个位置的元素是否存在,因为重复就要删掉

用pre数组维护这个元素重复的前一个位置,每次先删掉之前的重复元素再加当前的元素

#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 = 1e6 + 10;
int f[N], a[N], pre[N], n, m;

struct segment
{
	int l, r, id, val;
}s[N];

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

void add(int x, int p)
{
	for(; x < N; x += lowbit(x))
		f[x] += p;
}

int sum(int x)
{
	int res = 0;
	for(; x; x -= lowbit(x))
		res += f[x];
	return res;
}

bool cmp1(segment a, segment b)
{
	return a.r < b.r;
}

bool cmp2(segment a, segment b)
{
	return a.id < b.id;
}

int main()
{
	scanf("%d", &n);
	_for(i, 1, n) scanf("%d", &a[i]);
	scanf("%d", &m);
	_for(i, 1, m)
	{
		int l, r;
		scanf("%d%d", &l, &r);
		s[i] = {l, r, i, 0};
	}
	sort(s + 1, s + m + 1, cmp1);
	
	int r = 0;
	_for(i, 1, m)
	{
		if(s[i].r > r)
		{
			_for(j, r + 1, s[i].r)
			{
				if(pre[a[j]]) add(pre[a[j]], -1);
				pre[a[j]] = j;
				add(j, 1);
			}	
			r = s[i].r;
		}
		s[i].val = sum(s[i].r) - sum(s[i].l - 1);
	}
	
	sort(s + 1, s + m + 1, cmp2);
	_for(i, 1, m) printf("%d\n", s[i].val);
	
	return 0;
}

K. King's Task(思维)

这道cf1200的题竟然没做出来

原因是想复杂了

我一直在想怎么推出来答案

其实直接暴力枚举

因为两次同样操作就还原了,所以操作要交替进行

所以暴力枚举就好了

#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 a[N << 1], t[N << 1], n;

int work(int op)
{
	int res = 0;
	_for(i, 1, 2 * n) t[i] = a[i];
	while(1)
	{
		int ok = 1;
		_for(i, 1, 2 * n)
			if(t[i] != i)
			{
				ok = 0;
				break;
			}
		if(ok) return res;
		
		res++;
		
		if(op) for(int i = 1; i <= 2 * n; i += 2) swap(t[i], t[i + 1]);
		else _for(i, 1, n) swap(t[i], t[n + i]);
		op = op ^ 1;
		
		ok = 1;
		_for(i, 1, 2 * n)
			if(t[i] != a[i])
			{
				ok = 0;
				break;
			}
		if(ok) return 1e9;
	}
}

int main()
{
	scanf("%d", &n);
	_for(i, 1, 2 * n) scanf("%d", &a[i]);
	
	int ans = min(work(0), work(1));
	printf("%d\n", ans == 1e9 ? -1 : ans);
	
	return 0;
}

下周就是认真补题,以及学习题目中暴露出来的缺失的知识点,学一些高频知识点

如莫队,扫描线,主席树,简单dp,计算几何等等

那各种dp不着急

H. K and Medians(思维)

这是一道cf2200的思维题,我没做出来

首先一个很明显的就是一定要(n - m) mod (k - 1) = 0才可以

然后我们考虑最后一次删除,在某一个b位置,前面删掉(k - 1)/2, 后面删掉(k - 1)/2

也就是说无论如何一定要存在某个b,前面至少有(k - 1)/2个要删掉的,后面至少有(k - 1) / 2要删掉的

那么我们接下来考虑,多出来的部分怎么办

我们可以一直删,直到前面剩下(k-1)/2,后面也是

如果前面多的部分多就前面删的1数多一些,后面同理

因为(n - m) mod (k - 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 = 2e5 + 10;
int a[N], s1[N], s2[N], n, k, m;

int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		scanf("%d%d%d", &n, &k, &m);
		_for(i, 1, n) a[i] = 0;
		_for(i, 1, m)
		{
			int x; scanf("%d", &x);
			a[x] = 1;
		}
		
		if((n - m) % (k - 1) != 0)
		{
			puts("NO");
			continue;
		}
		
		s2[n + 1] = s1[0] = 0;
		_for(i, 1, n) s1[i] = s1[i - 1] + (a[i] == 0);
		for(int i = n; i >= 1; i--) s2[i] = s2[i + 1] + (a[i] == 0);
		
		int ok = 0;
		_for(i, 1, n)
			if(a[i] && s1[i - 1] >= (k - 1) / 2 && s2[i + 1] >= (k - 1) / 2)
			{
				ok = 1;
				break;
			}
		puts(ok ? "YES" : "NO");
	}

	return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值