当 k 与 n 互质时,容易给出一种构造。 因为 k 与 n 互质,所以 k 一定存在模 n 意义下的逆元,令 S0 的 k0,k1,⋯,kk−1 位置处(这里和后面的位置均是指在模 n 意义下)为 1 ,其余处为 0 。同样的对于 Si ,我们令其 ki,ki+1,⋯,kk+i−1 处为 1 ,其余处为 0 。显然,从 Si 变成 Si+1 可以通过向右循环移位 k1 实现,同时发现 ki+1=kk+(i+1)−1 ,于是我们将 Si 的 ki 位置变成 0 ,将 ki+1 位置变成 1,即可用 b 操作将 Si 变成 Si+1 。
#include<bits/stdc++.h> usingnamespace std;
intexgcd(int a, int b, int &x, int &y) { if(!b) return x = 1, y = 0, a; int d = exgcd(b, a%b, y, x); y -= (a/b)*x; return d; } intinv(int a, int p) { int x, y; exgcd(a, p, x, y); return (x%p+p)%p; }
constint N = 105;
char s[N];
voidsolve();
#define gcd(a, b) __gcd(a, b)
intmain() { int t; cin >> t; while(t--) solve(); }
voidsolve() { int n, k, l; cin >> n >> k >> l; if(gcd(n, k) != 1) returnvoid(cout << "NO\n"); cout << "YES\n"; int invk = inv(k, n); for(int i = 1; i <= l; i++) { memset(s, '0', sizeof(s)); for(int j = 0; j < k; j++) s[(i+j)*invk%n] = '1'; s[n] = '\0'; cout << s << '\n'; } }
DNA序列
Description
给定 n 个只包含 “A”,“T”,“C”,“G” 字符串,每个字符串取一个非空前缀,然后将这些前缀按任意顺序链接起来,问能获得的字典序最小的字符串是什么。
Input
第一行一个整数 n,表示字符串个数。
接下来 n 行,每行一个字符串。
Output
输出一行一个字符串。
Hint
1≤n,m≤50。
Solution
首先,每个字符串后面加一个很大的字符(例如"Z")。
对于每个字符串 s ,找到最短的前缀 x 满足 x∞<s ,然后将字符串 s 分成 xy+z ,使得 x 不是 z 的前缀。
那么合并的顺序一定是按照 x∞ ,字典序升序然后相同按照 z+′Z′ 的字典序降序。
我们可以考虑是每次我们希望找个最小的 x ,然后加入尽量多的 x,但是 x 加完之后我们希望能加的字符尽量小,所以我们把尾巴字典序最小的放后面。
知道顺序之后,倒着枚举前缀的长度暴力贪心就行了。
#include<bits/stdc++.h> usingnamespace std;
constint N = 55;
int n; string s[N]; string tmp, tmp1, tmp2, ans;
structnode { string s1, s2, s3; } a[N];
intmain() { cin >> n; for(int i = 1; i <= n; i++) cin >> s[i], a[i].s1 = s[i]; for(int i = 1; i <= n; i++) { int len = s[i].size(); for(int j = 0; j < len; j++) { tmp.clear(); for(int k = 0; k < 50; k++) tmp += s[i][k%(j+1)]; bool flag = true; for(int k = 0; k < len; k++) if(s[i][k] != tmp[k]) flag = false; if(flag or s[i] >= tmp) { s[i] = tmp.substr(0, j+1); break; } } a[i].s2 = s[i]; int len1 = a[i].s1.size(), len2 = a[i].s2.size(); for(int l = 0; l < len; l += len2) { int k = 0; for(; k < len2; k++) if(a[i].s1[k+l] != a[i].s2[k]) break; if(k >= len2) continue; for(k = l; k < len; k++) a[i].s3 += a[i].s1[k]; break; } tmp.clear(); for(int j = 0; j < 50; j++) tmp += a[i].s2[j%len2]; a[i].s2 = tmp; } sort(a+1, a+n+1, [&](node a, node b){ if(a.s2 != b.s2) return a.s2 < b.s2; else return a.s3+'Z' > b.s3+'Z'; }); for(int i = n; i >= 1; i--) { int len = a[i].s1.size(); for(int j = 0; j < len; j++) { tmp1.clear(); for(int l = 0; l <= j; l++) tmp1 += a[i].s1[l]; tmp1 += tmp2; if(j == 0or tmp1 < ans) ans = tmp1; } tmp2 = ans; } cout << ans << "\n"; }
探寻
Description
给定一棵 n 个点, n−1 条边的树,边带权,节点编号为 0∼n−1 ,其中根的编号为 0,一开始你在根上,第一次走一条边需要花与边权相同数目的钱,走过一次这条边后再走这条边就不需要花钱,如果钱不够则不能走,第一次走到一个点 x 可以获得 valx 的钱,之后再到达 x 就不会获得钱,标记其中一个叶子为目标节点,求出到达目标节点所需的最少的初始钱数。
Input
第一行一个整数 n。
接下来 n−1 行,每行三个整数 pi,costi,vali,表示节点 i 的父节点为 pi,与父节点所连的边的边权为 costi,第一次走到节点 i 可以获得 vali 的钱。
仅有一个叶子节点满足 vali=−1 ,该节点为目标节点。
Output
一个整数表示最少的初始钱数。
Hint
2≤n≤2×105,0≤costi,vali≤109。
Solution
每次只走一步的贪心显然是错的,因为没有考虑走完这一步后的情况,所以我们从 x 走到 y 的时候,需要考虑 y 整棵子树内的情况。
我们将走一个子树内的连通块成为一个决策,一个决策有两个属性 (need,get),代表使用这个决策的前提是有 need 的钱,使用完这个决策后的收益是 get,只有 get>0 的决策才是有用决策。
设当前节点为 x ,y∈sonx ,只走 x 这一个点的决策显然是 (costx,valx−costx),如果 valx−costx<0 ,那么就需要从 y 中选择决策增加 x 的收益,这个可以将决策按 need 比较,用可并堆维护子树内的决策即可,时间复杂度 O(nlogn)。