zoukankan      html  css  js  c++  java
  • 一些思维题(一)

    题目:

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

      

  • 相关阅读:
    批量新增百万条数据 十百万条数据
    sqlserver 组内排序
    EF ++属性会更新实体
    Entity Framework Core: A second operation started on this context before a previous operation completed
    abp Cannot access a disposed object. A common cause of this error is disposing
    abp xunit Can not register IHostingEnvironment. It should be a non-abstract class. If not, it should be registered before.”
    hangfire enqueued but not processing(hangfire 定时任务入队列但不执行)
    EF 更新实体 The instance of entity type 'BabyEvent' cannot be tracked because another instance
    datatable to entiy list 不支持可空类型和枚举类型
    webapi 设置不显示接口到swaggerUI
  • 原文地址:https://www.cnblogs.com/ruanbaitql/p/14350695.html
Copyright © 2011-2022 走看看