记录一下
C Cut
我们可以对数字进行一些有趣的游戏,比如,把数字全部切开又能变成多少?
你现在随意地写下了一个数字 (1≤n<10**10)n (1≤n<10**10),你现在可以任意多次从任意位置切开这个数字,随后将切开的数字加起来,得到一个结果。
你现在想知道所有可能的切法结果的和是多少。
input:
125output:
176
- 125
- 1+25=261+25=26
- 12+5=1712+5=17
- 1+2+5=81+2+5=8
思路:数字长度不会超过10位,直接暴力破解即可。复杂度大概在
void dfs(string n, int start, ll sum, ll& result){
if (start >= sz(n)) {result += sum; return;}
for (int len = 1, i = start; i + len <= sz(n); ++len){
ll t = stoll(n.substr(i, len));
dfs(n, i + len, sum + t, result);
}
}
int main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
string s;
cin >> s;
ll result = 0;
dfs(s, 0, 0, result);
cout <<result << endl;
return 0;
}
D Differ
作为一个居家小能手,你深知收纳之道。
至少,同一种东西不要放在一起,因为同样的东西放在一起容易分不出来……。
现在有 (1≤n≤1000) (1≤n≤1000) 个盒子,你现在有 (2≤k≤1000)k (2≤k≤1000) 种物品,每种物品的数量有无限个。你现在要在每个盒子里都放一个物品,同时,你不想相邻的位置上摆同样的东西。
想请问有多少种摆放方式可以满足你的要求。
思路:k个物品,n个槽,物品不连续,很容易想到对于当前位置i,能摆放的方法数为位置i-1所有的不相等的物品的方法数。转移方程有了。然后边界条件,如果只有1个槽,那么有k种方法,边界条件也有了,直接写就行,比赛的时候是写的bottom-up dp,现在写一下up-down形式的dp.
//up-down version
int dfs(vvi& memo, int n, int k, int curpos, int kth){
if (curpos == 1){return 1;}
int& ans = memo[curpos][kth];
if (ans != -1) return ans;
ans = 0;
for (int i = 1; i <= k; ++i)if (kth != i){
ans += dfs(memo, n, k, curpos - 1, i);
}
return ans;
}
int main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int n, k; cin >> n >> k;
vvi memo(n + 1, vi(k + 1, -1));
ll result = 0;
for (int i = 1; i <= k; ++i){
result += dfs(memo, n, k, n, i);
}
cout << result << endl;
return 0;
}
//bottom-up version
int main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int n, k;
cin >> n >> k;
vvi f(1234, vi(k + 1, 0));
for (int i = 0; i <= k; ++i){
f[1][i] = 1;
}
for (int i = 2; i <= n; ++i){
for (int j = 1; j <= k; ++j){
for (int g = 1; g <= k; ++g){
if (g == j){continue;}
f[i][j] += f[i - 1][g];
}
}
}
int sum = 0;
for (int i = 1; i <= k; ++i){
sum += f[n][i];
}
cout << sum << endl;
return 0;
}
5EchoN
很不错的一个题,字符串前缀匹配,数据范围是1e6。暴力破解超时,然后用了二分,时间复杂度是n * logn,奈何字符串相等判断消耗的时间太多,可能不是o1,最后还是TLE了。不甘心又写了个倍增,还是TLE,放弃了。记录一下三种实现的主代码
最后正解应该是KMP,然而不会写
// brute force
string s;
cin >> s;
int n = sz(s);
ll result = 0;
for (int i = 0; i < n; ++i){
for (int len = 1; len <= n; ++len){
if (s.substr(i, len) == s.substr(0, len)){
result += 1;
}
else{
break;
}
}
}
cout << reuslt << endl;
//binary search
string s;
cin >> s;
int n = sz(s);
ll result = 0;
for (int i = 0; i < n; ++i){
if (s[i] != s[0]) continue;
int l = 1, r = n;
while (l < r){
int mid = (l + r + 1) >> 1;
if (s.substr(i, mid) == s.substr(0, mid)){l = mid;}
else r = mid - 1;
}
if (s.substr(i, l) == s.substr(0, l)){
result += l;
}
}
cout << result << endl;
//倍增
string s;
cin >> s;
int n = sz(s);
ll result = 0;
for (int i = 0; i < n; ++i){
int m = 0, p = 1;
while (p!= 0 && m <= n){
if (s.substr(0, m + p) == s.substr(i, m + p)){
m += p;
p *= 2;
}
else p /= 2;
}
result += m >= n ? n : m;
//cout << i << " "<< result << endl;
}
cout << result << endl;
GcdGame
很有趣的一个现象:给定一个数组,然后给定数组上的两个位置,我们可以在这两个位置上左右移动(不越界的情况下),每次移动可以×移动到的方格的数字。
问:最少移动多少次可以实现两个位置不互质
思路:对于每个位置求出质因子,然后对于每个位置i,记录一下所有可能出现的质因子的距离(移动的步数)。对于两个位置x和y,根据记录的信息, 可以很快的筛选出哪个质因子是可以让它们移动最少的步数得到的。
奈何可惜的是,在记录每个位置的质因子时,数据结构出了一些问题...而且感觉也会面临MLE的问题。数据范围是1e5。
Jargonless
给定两个字符串s和t,我们可以做的操作是从s的左边或者右边删除>=0个字符,问有多少种删除方式,在删除字符操作后,t仍为s的子串。t为s的子串:s任意位置删除0或多个字符,t==s。
思路:有点抽象的题目,暴力破解的话就是试错,两个for循环,内层是判定t是否为s的子串,数据范围是3e5,不用想必然tle。于是需要找一下规律:
不知道怎么样,我们可以发现这样一个规律:如果当前s串的起点i到j表示的字符串,可以跟t进行匹配,那么j + 1到n的字符都可以删掉,从n开始删,删到j+1,一共是n - j种删除方式。
所以对于每个起点i,只要找到最近的能匹配到t字符串的右边界r,就可以O(1)的计算出以i为起点的删除元素的方法个数..时间复杂度骤降,但是如果有极端的testdata的话,还是有可能会TLE,因为找右边界是个技术活,很有可能就直接走到底了,结果还是On²..
为了避免这种情况的发生,就需要waset some capacity. Ok,我们从起点找右边界的目的是为了匹配字符串t,也就是说,我们遍历的时候所做的无效操作,是查找到了不需要的元素,如果能认识到这一点,解决掉这个不必要的查找,就可以避免TLE问题。 如果当前我们所在的位置是i,我们要匹配的元素是c,那么我们就可以想办法来缩短这个查找时间,在O1的时间内找到字符c。
我们需要一个映射数组,对于每个位置i,记录一下在它右边所有的字符的位置,这样就可以o1的查找到下一个下标,而不需要一个一个去筛选,我们只需要在每个位置上开一大小为26的数组来记录位置即可。
于是,时间复杂度变成了n * 200(字符串t的最大长度)
当然还有继续优化的方法:
1、如果当前起点开始查找,不能匹配子串t(当前位置记录的下一个位置中,没有t的最后一个元素),直接结束程序
2、。。。。
奈何写程序的时候忘记给计数的变量开大一点了,只开到了32位int型,不够用,答案一直错。debug了好久也没找出啥原因,哎,以后计数问题统统开Ull
int main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int inf = 0x3f3f3f3f;
string s, t; cin >> s >> t;
int n = sz(s), m = sz(t);
vvi a(n + 1, vi(26, inf));
vi b(26, inf);
char x = ‘a';
for (int i = n - 1; i >= 0; --i){
a[i] = b; b[s[i] - x] = i;
}
ll result = 0;
for (int i = 0; i < n; ++i){
int r = i;
if (a[r][t[m - 1] - x] == inf) break; //一个小的prune操作,不加也不会TLE
bool ok = true;
int j = 0;
if (s[r] == t[0]) j = 1; //如果当前起点刚好是t的第一个元素
for (j; j < m; ++j){
auto c = t[j];
r = a[r][c - x];
if (r == inf) {ok = false; break;}
}
if (ok == false) continue;
result += n - r;
}
cout <<result << endl;
return 0;
}
GcdGame
GcdGame代码更新,比赛已结束不能提交代码,不知道这种数据结构会不会tle或者mle.
思路:先从左往右遍历,对于每个value,分解一下质因子,并存到map里面,然后每个位置放了一个map,map的第一个值是质因子的值,第二个值是数组下标的值,数组下标从0开始。
从左往右遍历完后,每个位置都存储了它左边距离它最近的质因子的下标,然后从右往左扫描:仍然是分解质因子,然后将分解后的质因子存储到一个临时map中。分解完成后,对临时map进行遍历,并与当前的位置的质因子进行对比,如果临时map中的质因子距离小于之前已经扫描好左边的质因子的距离,那么就更新一下当前质因子的下标。
这样最后得到的一个存储了map的vector,记录了每个位置的到所有质因子的距离...然而数据最大值是1e8,也就是说,质因子的范围可能是1e4量级的,对于数据长度1e5而言,很有可能MLE
这里还有个可以优化的地方,就是分解质因子的时候记录一下,再次遇到相同的数据的时候可以直接用。
上代码
int main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int n, q;
cin >> n >> q;
vector<map<int, int>> vm(n);
map<int, int> tmap;
vi a(n);
for (int i = 0; i < n; ++i){
int x; cin >> x; a[i] = x;
for (int j = 2; j <= x; ++j){
if (x % j == 0){
while (x % j == 0) x /= j;
tmap[j] = i;
}
}
if (x > 1) tmap[x] = i;
vm[i] = tmap;
}
tmap.clear();
for (int i = n - 1; i >= 0; --i){
int x = a[i];
for (int j = 2; j <= x; ++j){
if (x % j == 0){
while (x % j == 0) x /= j;
tmap[j] = i;
}
}
if (x > 1) tmap[x] = i;
for (auto v : tmap){
int prime = v.first;
if(vm[i].count(prime)){
int dis = abs(i - vm[i][prime]);
if (dis > abs(i - v.second)){
vm[i][prime] = v.second;
}
}
}
}
for (int i = 0; i < q; ++i){
int x, y, result = 1e8; cin >> x >> y;
x -= 1, y -= 1; //输入下标从1开始
for (auto v : vm[x]){
int prime = v.first;
//if (vm[y].count(prime) == 0) continue;// 可以删掉,因为左右两边都扫描以后,两个位置的必定都含有了所有质因子
result = min(result, abs(y - vm[y][prime]) + abs(x - vm[x][prime]));
}
cout << result << "\n";
}
return 0;
}