• 一些思维题(一)


    题目:

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

      

  • 相关阅读:
    编译Excel遇到的DialogBoxW宏的实参不足问题
    C# 简单连接数据库并执行SQL查询语句
    AutoCAD VBA 遍历所有对象
    VBA: 错误消息:"类未注册"插入用户窗体
    解决Qt程序发布时中文乱码问题
    Qt操作excel
    HWND_BROADCAST的一个用法——修改环境变量,立即通知系统
    VC环境使用XML解析器(TinyXML)编程
    C++ XML解析之TinyXML篇(转载)
    C/C++枚举目录中的文件或文件夹
  • 原文地址:https://www.cnblogs.com/ruanbaitql/p/14350695.html
Copyright © 2020-2023  润新知