一些思维题(一)

题目:

ICPC南京 K Co-prime Permutation

题意:

给定n,k,构造一个n的全排列p[1],p[2]....p[n],这个全排列要满足恰好有k个p[ i ] 和 i 互质

1<=n<=1e6,0<=k<=n

 

CF 1336B Xenia and Colorful Gems 

题意:

给nr个红色的正整数,ng个绿色的正整数,nb个蓝色的正整数

每种颜色各选出一个数字,记为x,y,z,要使(xy)2+(yz)2+(zx)2最小

t组输入,t<=100, 1 <=nr,ng,nb(都是t组的总和)<=1e5

 

CF #635(div1)A. Linova and Kingdom

题意:

给一颗节点数为n的树,给定k,要使这棵树上有k个A节点,n-k个B节点,每个A节点会产生一个贡献,其贡献为该节点到根节点路径上的B节点的个数,问最大贡献是多少

2<=n<=2e5, 1<=k<n

 

CF 1468J Road Reform

题意:

给一个n个节点,m条边的无向联通图,给定k,有两种操作,一种是删除一条边,另一种是把边的权值加一或减一,目标是把这个图变成一颗树,并且这棵树上的最大权值边的权值恰好为k,求第二种操作的操作数最少为多少

2<=n<=2e5,n1mmin(2e5,n(n1)/2),1<=k<=1e9, 1<=边的权值<=1e9

 

牛客9981C 红和蓝

题意:

给一颗n个节点的树,要把这棵树上的所有节点染成红色或蓝色,要求每个节点的周围节点(即与之相连的节点)有且仅有一个节点颜色与之相同,输出任意的染色方案,若不存在则输出-1

1<=n<=1e5

 

思路/做法:

ICPC南京 K Co-prime Permutation

 

构造题

n很大,看起来很难,素数分解后也不知道怎么构造

 

要用一个常用的结论:i和i+1互质

结论证明:更相减损即可

 

 

还有一个结论:1和所有数互质

 

当k = 0时是不存在的

利用这两个结论,1 <= i <= k-1 ,p[i] = i+1,然后p[k] = 1,后面的p[i] = i 即可

 

CF 1336B Xenia and Colorful Gems 

看起来很难,而且无从下手

任选3个数,情况太多了

所以想想先任选第一个数,剩下的两个数怎么选,

遇到这种问题,要确定一个方向性,比如说,先选择的第一个数是这三个数最大的,第二个数是这三个数第二大的,用这种方法来遍历

 

 

以先选x,再选y,再选z为例,先选一个x,然后遍历y,y要<=x,然后选定z,z要<=y,并且这个z要最接近y

y是必须遍历的,而不是直接选择一个最接近x的y,可以证明y最接近x的情况不一定是最优的

但是y不需要每次都重新遍历,就是说,每次选择了一个x,y不需重新从头开始遍历,可以证明:比如说当x选x[2]时,y可以选y[1]到y[12],y选y[10]是最优的,当x选x[3]时,比如这时y可以选到y[15],y不需要从y[1]遍历到y[10],而是从y[13]遍历到y[15],因为y[10]是x选x[2]时,y在y[1]到y[12]里最优的,y[10]也一定是x选x[3]时,y在y[1]到y[12]里最优的,所以我们只要维护之前最优的位置(y[10])即可

可以用尺取法遍历x,y,选择z

代码:

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
const int MAXN = 1e5+7;
long long r[MAXN],g[MAXN],b[MAXN];
int nr,ng,nb;
long long qiu(long long a,long long b,long long c) {
	return (a-b)*(a-b) + (a-c)*(a-c) + (b-c)*(b-c);
}
long long ans;
void solve(long long r[],int nr,long long g[],int ng,long long b[],int nb){
	int pos1 = 1,pos2 = 1,pos3 = 1;
	int pp2 = 0,pp3 = 0; //pp2来记录pos2最佳的位置 
	long long res = qiu(r[1],g[1],b[1]);
	for(;pos1 <= nr;pos1++){//遍历x 
		while(g[pos2] < b[pos3]) pos2++;
		while(r[pos1] < g[pos2]) pos1++;
		if(pos1>nr||pos2>ng||pos3>nb) break;
		if(pp2 && pp3) res = min(res,qiu(r[pos1],g[pp2],b[pp3]));
		for(;pos2<=ng&&g[pos2]<=r[pos1];pos2++){//遍历y,pos2不从1开始 
			for(;pos3<=nb&&b[pos3]<=g[pos2];pos3++);
			pos3--;
			if(!pos3) continue; 
			long long tmp = qiu(r[pos1],g[pos2],b[pos3]);
			if(tmp < res){
				pp2 = pos2;//记录最佳位置 
				pp3 = pos3;
				res = tmp;
			}
		}
		pos2--;
	}
	ans = min(ans,res);
}
int main()
{
	int T;
	cin>>T;
	while(T--){
		scanf("%d%d%d",&nr,&ng,&nb);
		for(int i = 1;i<=nr;i++) scanf("%lld",&r[i]);sort(r,r+nr+1);
		for(int i = 1;i<=ng;i++) scanf("%lld",&g[i]);sort(g,g+ng+1);
		for(int i = 1;i<=nb;i++) scanf("%lld",&b[i]);sort(b,b+nb+1);
		ans = qiu(r[1],g[1],b[1]);
		solve(r,nr,g,ng,b,nb); 
		solve(g,ng,b,nb,r,nr);
		solve(b,nb,r,nr,g,ng);
		solve(r,nr,b,nb,g,ng);
		solve(g,ng,r,nr,b,nb);
		solve(b,nb,g,ng,r,nr); 
		printf("%lld\n",ans);
	}
	return 0;
}

  

还有一种更简单的做法,是我看了题解才会的

考虑选择出来的最优的x,y,z,x一定是所有大于y的x[i]里最小的,z一定是所有小于y的z[i]里最大的

所以我们遍历y,然后用二分选择出x,z即可

代码:

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
const int MAXN = 1e5+7;
long long r[MAXN],g[MAXN],b[MAXN];
int nr,ng,nb;
long long qiu(long long a,long long b,long long c) {
	return (a-b)*(a-b) + (a-c)*(a-c) + (b-c)*(b-c);
}
long long ans;
void solve(long long r[],int nr,long long g[],int ng,long long b[],int nb){
	for(int pos2 = 1;pos2 <= ng;pos2++){
		int pos1 = lower_bound(r+1,r+nr+1,g[pos2]) - r;
		int pos3 = lower_bound(b+1,b+nb+1,g[pos2]) - b;
		if(pos3>nb) pos3 = nb;
		else if(b[pos3] > g[pos2]) pos3--;
		if(!pos1||!pos3|| pos1 > nr) continue;
		ans = min(ans,qiu(r[pos1],g[pos2],b[pos3]));
	}
}
int main()
{
	int T;
	cin>>T;
	while(T--){
		scanf("%d%d%d",&nr,&ng,&nb);
		for(int i = 1;i<=nr;i++) scanf("%lld",&r[i]);sort(r,r+nr+1);
		for(int i = 1;i<=ng;i++) scanf("%lld",&g[i]);sort(g,g+ng+1);
		for(int i = 1;i<=nb;i++) scanf("%lld",&b[i]);sort(b,b+nb+1);
		ans = qiu(r[1],g[1],b[1]);
		solve(r,nr,g,ng,b,nb); 
		solve(g,ng,b,nb,r,nr);
		solve(b,nb,r,nr,g,ng);
		solve(r,nr,b,nb,g,ng);
		solve(g,ng,r,nr,b,nb);
		solve(b,nb,g,ng,r,nr); 
		printf("%lld\n",ans);
	}
	return 0;
}

  

 

CF #635(div1)A. Linova and Kingdom

如果k很小,那就是树型dp的题目了,然而这k好大,怎么办

我也不知道怎么办,只好去贪心做

如果k=1,那我就把深度最大的节点变成A节点,那该A节点的贡献就等于该A节点的深度

考虑选多个A节点的情况,当把一个节点变成A节点,该节点的贡献为深度,同时以该节点为子树,子树中的所有A节点(除子树,根节点外)的贡献减一,

那么我就把这个A节点的贡献用 深度 - (子树中A节点个数-1)来计算,

可以发现,要把一个节点变成A节点,一定是先把其子树内的所有节点变成A节点是更优的,

证明:如果选了K个A节点,当这K个A节点中存在一个A节点可以移动到儿子节点(即把这个A节点变成B节点,它的一个儿子节点变成A节点),那么如果把这个A节点移动到儿子节点,贡献一定会增加,

所以,这种状态一定不是最优的。

所以我们每次选择一个节点,可以认为其子树内的所有节点都已经是A节点,这样该节点的贡献就为 深度 - (子树大小-1),

做法就是,对每个节点计算它的贡献(深度 - 子树大小 + 1),然后对贡献排序,计算前K大的所有贡献之和。

 

如何思考该题?

先考虑K很小的情况,然后自己想想怎么选好,就能得出先选深度大的,然后发现自己选出的所有的节点,其子树中的节点都是A节点,可以猜就是这样贪心的,

这样贪心以后,不难计算出每个节点变成A节点带来的贡献,然后排序即可

 

代码:

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
const int MAXN = 2e5 + 7;
struct EDGE {
	int to, next;
}edge[MAXN*2];
int head[MAXN], tot = 0;
void add(int u, int v) {
	tot++;
	edge[tot].to = v;
	edge[tot].next = head[u];
	head[u] = tot;
}
int deep[MAXN],siz[MAXN],gong[MAXN];
void dfs(int s, int fa) {
	siz[s] = 1;
	for (int i = head[s]; i; i = edge[i].next) {
		int po = edge[i].to;
		if (po == fa)continue;
		deep[po] = deep[s] + 1;
		dfs(po, s);
		siz[s] += siz[po];
	}
	gong[s] = deep[s] - siz[s] + 1;
}
bool cmp(int x, int y) {
	return y < x;
}
int main()
{
	int n, k;
	cin >> n >> k;
	int u, v;
	for (int i = 1; i <= n - 1; i++) {
		scanf("%d%d", &u, &v);
		add(u, v);
		add(v, u);
	}
	deep[1] = 0;
	dfs(1, 0);
	long long ans = 0;
	sort(gong + 1, gong + n + 1, cmp);
	for (int i = 1; i <= k; i++) {
		ans += (long long)gong[i];
	}
	cout << ans << endl;
	return 0;
}

  

CF 1468J Road Reform

怎么选?

这里是要选出一颗树,有个算法也是选出一颗树的,叫最小生成树

 

 

那就很好办了,做过次小生成树的,知道一个常用的结论:先造一个最小生成树,然后再任加一条边,再删去一条边,就得到另一棵树

这题也是一样,我们先造一个最小生成树,记最小生成树里的最大权值边的权值为MA

如果MA>=K,就把这棵树所有权值大于K的边的权值减为K

如果MA<K,则在剩下的边里找到最接近K的边,这条边的权值记为DA,则ans = abs(DA-K), 原因是我在这棵树里加了DA这条边,变成了基环树,然后又在基环树的环里删掉一条边,就得到了一颗树,这棵树的n-2条边的权值都是<=MA<K的,而权值最大的边的权值为DA

 

这里推荐一题:次小生成树(洛谷上搜)。

代码:

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
const int MAXN = 2e5 + 7;
struct EDGE {
	int u,v,w;
}edge[MAXN];
int par[MAXN];
int find(int x) {
	if (par[x] == x) return x;
	return par[x] = find(par[x]);
}
bool cmp(EDGE a, EDGE b) {
	return b.w > a.w;
}
int main()
{
	int T, n, m, k;
	cin >> T;
	while (T--) {
		cin >> n >> m >> k;
		for (int i = 1; i <= n; i++) {
			par[i] = i;
		}
		int u, v, w;
		for (int i = 1; i <= m; i++) {
			scanf("%d%d%d", &edge[i].u, &edge[i].v, &edge[i].w);
		}
		sort(edge + 1, edge + m + 1, cmp);
		int ma = 0;
		long long ans = 0;
		for (int i = 1,e = 0; e < n - 1; i++) {
			u = edge[i].u, v = edge[i].v, w = edge[i].w;
			int fa = find(u), fb = find(v);
			if (fa == fb) continue;
			par[fb] = fa;
			e++;
			ma = max(ma, w);
			if (w > k) {
				ans += (long long)(w - k);
			}
		}
		if (ma >= k) cout << ans << endl;
		else {
			ans = (long long)k - ma;
			for (int i = 1; i <= m; i++) {
				ans = min(ans, (long long)abs(edge[i].w - k));
			}
			cout << ans << endl;
		}
	}
	return 0;
}

 

牛客9981C 红和蓝

该从哪里选颜色?

关键是发现叶子节点一定与父节点同色

然后是爆搜每个叶子节点的颜色?这样复杂度太大

结论:这种题应该尽可能确定更多的关系,再确定颜色

我们发现了叶子节点与父节点同色这一点,所以就设same[i]来表示其与父节点是同色还是异色,这样以关系为状态,推出更多的关系

代码:

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
const int MAXN = 1e5 + 7;
struct EDGE {
	int to, next;
}edge[MAXN * 2];
int head[MAXN], tot = 0;
int n;
void add(int u, int v) {
	tot++;
	edge[tot].to = v;
	edge[tot].next = head[u];
	head[u] = tot;
}
int fa[MAXN], siz[MAXN], same[MAXN];
bool flag = true;
int color[MAXN];
void dfs(int s, int f) {
	siz[s] = 1;
	int cnt = 0;
	for (int i = head[s]; i; i = edge[i].next) {
		int po = edge[i].to;
		if (po == f) continue;
		fa[po] = s;
		dfs(po, s);
		siz[s] += siz[po];
		if (same[po] == 1) {
			cnt++;
		}
	}
	if (cnt == 1) same[s] = -1;
	else if (cnt == 0) same[s] = 1;
	else flag = false;
	if (siz[s] == 1) {
		same[s] = 1;
	}
}
void lan(int s) {
	for (int i = head[s]; i; i = edge[i].next) {
		int po = edge[i].to;
		if (po == fa[s]) continue;
		if (same[po] == 1) {
			color[po] = color[s];
		}
		else {
			color[po] = 1 - color[s];
		}
		lan(po);
	}
}
int main()
{
	cin >> n;
	int u, v;
	for (int i = 1; i <= n - 1; i++) {
		scanf("%d%d", &u, &v);
		add(u, v);
		add(v, u);
	}
	dfs(1, -1);
	if (flag && same[1] != 1) {
		color[1] = 1;
		lan(1);
		for (int i = 1; i <= n; i++) {
			if (color[i] == 1) printf("R");
			else printf("B");
		}
		printf("\n");
	}
	else cout << "-1\n";
	return 0;
}

  

 

posted @ 2021-03-05 15:31  beta_dust  阅读(155)  评论(0编辑  收藏  举报