一些思维题(一)
题目:
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,要使(x−y)2+(y−z)2+(z−x)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,n−1≤m≤min(2e5,n(n−1)/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; }